/*
 *   kioskrun.cpp
 *
 *   Copyright (C) 2004 Waldo Bastian <bastian@kde.org>
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License version 2 as
 *   published by the Free Software Foundation.
 *
 *   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 "kioskrun.h"

#include "config.h"

#include <assert.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>

#include <tqdir.h>
#include <tqfile.h>

#include <tdeapplication.h>
#include <tdecmdlineargs.h>
#include <tdeconfig.h>
#include <kdebug.h>
#include <tdelocale.h>
#include <tdemessagebox.h>
#include <kprocess.h>
#include <ksavefile.h>
#include <ksimpleconfig.h>
#include <kstandarddirs.h>
#include <kurl.h>
#include <kuser.h>

#include "kiosksync.h"

#include <tdeio/netaccess.h>
#define NETACCESS	TDEIO::NetAccess

#undef DEBUG_ENTRIES

KioskRun *KioskRun::s_self = 0;

KioskRun::KioskRun( TQObject* parent, const char* name)
 : TQObject(parent, name), m_dcopClient(0), m_instance(0), m_localKdercConfig(0)
{
   m_noRestrictions = false;
   m_forceSycocaUpdate = false;
   s_self = this;
   m_saveConfigCache.setAutoDelete(true);
   m_immutableStatusCache.setAutoDelete(true);
   m_homeDir = TQDir::homeDirPath()+"/.trinity-test";
   TDECmdLineArgs *args = TDECmdLineArgs::parsedArgs();
   m_tderc = TQFile::decodeName(args->getOption("tderc"));
   m_isRoot = (getuid() == 0);
}

KioskRun::~KioskRun()
{
   shutdownRuntimeEnv();
   s_self = 0;
}

void
KioskRun::setUser(const TQString &user)
{
   if (m_user == user) return;

   shutdownRuntimeEnv();
   shutdownConfigEnv();
   m_user = user;
}

static void filterDupes(TQStringList &list)
{
   TQStringList tmp;
   for(TQStringList::ConstIterator it = list.begin();
       it != list.end(); ++it)
   {
      if (!tmp.contains(*it))
         tmp.append(*it);
   }
   list = tmp;
}

void
KioskRun::setKdeDirs(const TQStringList &dirs)
{
   if (m_kdeDirs == dirs) return;
   
   shutdownRuntimeEnv();
   shutdownConfigEnv();
   m_kdeDirs = dirs;
   TQStringList xdgDataDirs = TQStringList::split(':', TQFile::decodeName(getenv("XDG_DATA_DIRS")));
   if (xdgDataDirs.isEmpty())
   {
      xdgDataDirs = TQStringList::split(':', TDEGlobal::dirs()->kfsstnd_prefixes());
      xdgDataDirs.pop_front();
      for(TQStringList::Iterator it = xdgDataDirs.begin();
          it != xdgDataDirs.end(); ++it)
      {
         *it += "share";
      }
      xdgDataDirs << "/usr/local/share" << "/usr/share";
   }

   m_xdgDataDirs.clear();
   for(TQStringList::ConstIterator it = dirs.begin();
       it != dirs.end(); ++it)
   {
      m_xdgDataDirs.append(*it+"/share");
   }
   m_xdgDataDirs += xdgDataDirs;
   filterDupes(m_xdgDataDirs);

   TQStringList xdgConfigDirs = TQStringList::split(':', TQFile::decodeName(getenv("XDG_CONFIG_DIRS")));
   if (xdgConfigDirs.isEmpty())
   {
      xdgConfigDirs << "/etc/xdg";
      TQString sysconfMenuDir = TDEGlobal::dirs()->findDirs("xdgconf-menu", TQString()).last();
      if (sysconfMenuDir.endsWith("/menus/"))
         xdgConfigDirs << sysconfMenuDir.left(sysconfMenuDir.length()-7);
      
   }
   
   m_xdgConfigDirs.clear();
   for(TQStringList::ConstIterator it = dirs.begin();
       it != dirs.end(); ++it)
   {
      m_xdgConfigDirs.append(*it+"/etc/xdg");
   }
   
   m_xdgConfigDirs += xdgConfigDirs;
   filterDupes(m_xdgConfigDirs);
}

void
KioskRun::deleteDir(const TQString &dir)
{
   if (dir.length() <= 1) // Safety
      return;
   if (!dir.startsWith("/")) // Safety
      return;
   Q_ASSERT(dir.startsWith(m_homeDir));
    
   TDEProcess proc;
   proc << "rm" << "-rf" << dir;
   proc.start(TDEProcess::Block);
}

void
KioskRun::applyEnvironment(TDEProcess *p)
{
   p->setEnvironment("HOME", m_homeDir);
   p->setEnvironment("TDEHOME", m_homeDir+"/.trinity");
   p->setEnvironment("TDEROOTHOME", m_homeDir+"/.trinity");
   p->setEnvironment("TDEDIRS", m_kdeDirs.join(":"));
   p->setEnvironment("XDG_DATA_HOME", m_homeDir+"/.local/share");
   p->setEnvironment("XDG_DATA_DIRS", m_xdgDataDirs.join(":"));
   p->setEnvironment("XDG_CONFIG_HOME", m_homeDir+"/.config");
   p->setEnvironment("XDG_CONFIG_DIRS", m_xdgConfigDirs.join(":"));
   p->setEnvironment("DCOPAUTHORITY", m_homeDir+"/.trinity/DCOPserver");
   p->setEnvironment("TDE_KIOSK_NO_PROFILES", "true");
   if (m_noRestrictions)
      p->setEnvironment("TDE_KIOSK_NO_RESTRICTIONS", "true");
}

bool
KioskRun::prepare()
{
   bool result = setupRuntimeEnv();
   
   deleteDir(m_configDir);
   deleteDir(locateLocal("data"));
   deleteDir(m_desktopPath);
   deleteDir(m_homeDir+"/.config");
   deleteDir(m_homeDir+"/.local/share");
   return result;
}

void
KioskRun::updateSycoca()
{
   // Force update
   TQString sycocaUpdateFile = KioskRun::self()->locateLocal("services", "update_tdesycoca");
   TQFile file(sycocaUpdateFile);
   file.remove();
   file.open(IO_WriteOnly);
   file.close();

   dcopRef("kded", "tdebuildsycoca").call("recreate");
}

TDEProcess*
KioskRun::run(const TQString &cmd, const TQStringList &args)
{
   TDEProcess *proc = new TDEProcess(this);
   
   applyEnvironment(proc);

   *proc << cmd;
   *proc << args;

   proc->start(TDEProcess::NotifyOnExit);
   return proc;
}

class SetEnv
{
public:
   SetEnv(const char *key, const TQString &value) : m_key(key)
   {
      m_oldValue = getenv(m_key);
      setenv(m_key, TQFile::encodeName(value), 1);
   }
   
   ~SetEnv()
   {
      if (m_oldValue.isEmpty())
         setenv(m_key,"",1);
     else
         setenv(m_key,m_oldValue.data(),1);
   }

private:   
   const char* m_key;
   TQCString m_oldValue;
};

void
KioskRun::setupConfigEnv()
{
   if (m_instance) return;

   // ::locateLocal must be called before we change the env. vars!   
   TQString newTmpDir = ::locateLocal("tmp", "kioskdir");
   TQString newSocketDir = ::locateLocal("socket", "kioskdir");

   SetEnv home("HOME", m_homeDir);
   TQString kdeHome = m_homeDir+"/.trinity";
   SetEnv tdehome("TDEHOME", kdeHome);
   SetEnv tderoothome("TDEROOTHOME", kdeHome);
   SetEnv tdedirs("TDEDIRS", m_kdeDirs.join(":"));
   SetEnv xdgDataHome("XDG_DATA_HOME", m_homeDir+"/.local/share");
   SetEnv xdgDataDirs("XDG_DATA_DIRS", m_xdgDataDirs.join(":"));
   SetEnv xdgConfigHome("XDG_CONFIG_HOME", m_homeDir+"/.config");
   SetEnv xdgConfigDirs("XDG_CONFIG_DIRS", m_xdgConfigDirs.join(":"));

   ::mkdir(TQFile::encodeName(m_homeDir), 0700);
   ::mkdir(TQFile::encodeName(kdeHome), 0700);

   // Create temp & socket dirs.
   char hostname[256];
   hostname[0] = 0;
   gethostname(hostname, 255);

   TQString tmpDir = TQString("%1/%2-%3").arg(kdeHome).arg("tmp").arg(hostname);
   deleteDir(tmpDir);
   ::mkdir(TQFile::encodeName(newTmpDir), 0700);
   ::symlink(TQFile::encodeName(newTmpDir), TQFile::encodeName(tmpDir));

   TQString socketDir = TQString("%1/%2-%3").arg(kdeHome).arg("socket").arg(hostname);
   deleteDir(socketDir);
   ::mkdir(TQFile::encodeName(newSocketDir), 0700);
   ::symlink(TQFile::encodeName(newSocketDir), TQFile::encodeName(socketDir));

   m_configDir = TQString("%1/.trinity/share/config/").arg(m_homeDir);

   m_instance = new TDEInstance("kioskrun");
   (void) m_instance->dirs(); // Create TDEStandardDirs obj

   m_desktopPath = m_homeDir + "/Desktop/";
   m_desktopPath = m_instance->config()->readPathEntry( "Desktop", m_desktopPath);
   m_desktopPath = TQDir::cleanDirPath( m_desktopPath );
   if ( !m_desktopPath.endsWith("/") )
      m_desktopPath.append('/');
   
   {
      SetEnv tdehome("TDEHOME", "-");
      SetEnv tderoothome("TDEROOTHOME", "-");
      SetEnv xdgDataHome("XDG_DATA_HOME", m_xdgDataDirs.first());
      SetEnv xdgConfigHome("XDG_CONFIG_HOME", m_xdgConfigDirs.first());

      m_saveInstance = new TDEInstance("kioskrun");
      (void) m_saveInstance->dirs(); // Create TDEStandardDirs obj
   }
}

TQString
KioskRun::locate(const char *resource, const TQString &filename)
{
   setupConfigEnv();

   return m_saveInstance->dirs()->findResource(resource, filename);
}

TQString
KioskRun::locateSave(const char *resource, const TQString &filename)
{
   setupConfigEnv();

   // split path from filename
   int slash = filename.findRev('/')+1;
   TQString dir = filename.left(slash);
   TQString file = filename.mid(slash);
   return m_saveInstance->dirs()->saveLocation(resource, dir, false) + file;
}

TQString
KioskRun::locateLocal(const char *resource, const TQString &filename)
{
   setupConfigEnv();

   // split path from filename
   int slash = filename.findRev('/')+1;
   TQString dir = filename.left(slash);
   TQString file = filename.mid(slash);
   return m_instance->dirs()->saveLocation(resource, dir, true) + file;
}


void
KioskRun::shutdownConfigEnv()
{
   if (!m_instance) return;
   
   delete m_instance;
   m_instance = 0;
}

class ImmutableStatus
{
public:
   bool m_fileScope;
   TQDict<int> m_lines;
   TQString m_tmpFile;
   bool m_dirty;
};


bool
KioskRun::isConfigImmutable(const TQString &filename, const TQString &group)
{
   (void) configFile(filename);
   ImmutableStatus *status = m_immutableStatusCache.find(filename);
   assert(status);
   if (group.isEmpty())
      return status->m_fileScope;

   return status->m_lines.find(group);
}

void
KioskRun::setConfigImmutable(const TQString &filename, const TQString &_group, bool bImmutable)
{
   (void) configFile(filename);
   ImmutableStatus *status = m_immutableStatusCache.find(filename);
   assert(status);
   if (_group.isEmpty())
   {
      if (status->m_fileScope != bImmutable)
      {
         status->m_fileScope = bImmutable;
         status->m_dirty = true;
         m_forceSycocaUpdate = true;
      }
   }
   else
   {
      TQString group = TQString("[%1]").arg(_group);
      if (status->m_lines.find(group))
      {
         if (!bImmutable)
         {
            status->m_lines.remove(group);
            status->m_dirty = true;
            m_forceSycocaUpdate = true;
         }
      }
      else
      {
         if (bImmutable)
         {
            status->m_lines.insert(group, (int *) 1);
            status->m_dirty = true;
            m_forceSycocaUpdate = true;
         }
      }
   }
}

static void stripImmutable(TQString &ext)
{
   ext.replace("i", "");
   if (ext == "[$]")
      ext = TQString();
}

static void addImmutable(TQString &ext)
{
   ext.replace("[$", "[$i");
}

TQString
KioskRun::saveImmutableStatus(const TQString &filename)
{
   ImmutableStatus *status = new ImmutableStatus;
   status->m_fileScope = false;
   status->m_dirty = false;
   m_immutableStatusCache.insert(filename, status);
   
   KTempFile tmp;
   tmp.close();
   
   TQString newPath = tmp.name();
   status->m_tmpFile = tmp.name();
   
   TQString path = m_saveInstance->dirs()->findResource("config", filename);
   if (path.isEmpty())
      return newPath; // Nothing to do
      
   TQFile oldCfg(path);
   
   if (!oldCfg.open( IO_ReadOnly ))
      return newPath; // Error
   
   TQFile newCfg(newPath);
   if (!newCfg.open( IO_WriteOnly ))
      return newPath; // Error
   
   TQTextStream txtIn(&oldCfg);
   txtIn.setEncoding(TQTextStream::UnicodeUTF8);

   TQTextStream pTxtOut(&newCfg);
   pTxtOut.setEncoding(TQTextStream::UnicodeUTF8);
   
   TQRegExp immutable("(\\[\\$e?ie?\\])$");
   
   // TODO: Use "group+key" instead of "key" as index, otherwise it might not be unique

   while(! txtIn.atEnd())
   {
      TQString line = txtIn.readLine().stripWhiteSpace();
      
      if (line.startsWith("#"))
      {
        // Comment, do nothing...
      }
      else if (line.startsWith("["))
      {
        int pos = immutable.searchRev(line);
        if (pos != -1)
        {
           TQString group = line.left(pos);
           TQString ext = immutable.cap(0);
           stripImmutable(ext);
           if (pos == 0)
           {
              status->m_fileScope = true;
              continue;
           }
           status->m_lines.replace(group, (int *)1 );
           line = group + ext;
        }
      }
      else
      {
        int equal = line.find('=');
        if (equal != -1)
        {
          TQString key = line.left(equal).stripWhiteSpace();
          int pos = immutable.searchRev(key);
          if (pos != -1)
          {
             key = key.left(pos);
             TQString ext = immutable.cap(0);
             stripImmutable(ext);
             status->m_lines.replace(key, (int *)1 );
             line = key + ext + line.mid(equal);
          }
        }
      }

      pTxtOut << line << endl;
   }
   oldCfg.close();
   newCfg.close();
   
   if (newCfg.status() != IO_Ok )
   {
      kdWarning() << "Error writing " << newPath << endl;
      return newPath;
   }
   return newPath;
}

bool
KioskRun::restoreImmutableStatus(const TQString &filename, bool force)
{
   ImmutableStatus *status = m_immutableStatusCache.take(filename);
   if (!status)
   {
      kdDebug() << "KioskRun::restoreImmutableStatus(" << filename << ") status info missing" << endl;
      return true;
   }
   if (!force && !status->m_dirty)
   {
      kdDebug() << "KioskRun::restoreImmutableStatus(" << filename << ") not changed" << endl;
      delete status;
      return true;
   }
   kdDebug() << "KioskRun::restoreImmutableStatus(" << filename << ") restoring" << endl;

   TQString path = status->m_tmpFile;
   
   KSaveFile newCfg(path);
   if (newCfg.status() != 0)
   {
      delete status;
      return true; // Continue
   }

   TQTextStream *pTxtOut = newCfg.textStream();
   pTxtOut->setEncoding(TQTextStream::UnicodeUTF8);
   
   TQRegExp option("(\\[\\$e\\])$");

   if (status->m_fileScope)
   {
      kdDebug() << "Marking file " << filename << " immutable" << endl;
      (*pTxtOut) << "[$i]" << endl;
   }

   TQFile oldCfg(path);
   if (oldCfg.open( IO_ReadOnly ))
   {

      TQTextStream txtIn(&oldCfg);
      txtIn.setEncoding(TQTextStream::UnicodeUTF8);
   
      while(! txtIn.atEnd())
      {
         TQString line = txtIn.readLine().stripWhiteSpace();
      
         if (line.startsWith("#"))
         {
            // Comment, do nothing...
         }
         else if (line.startsWith("["))
         {
            if (status->m_lines.take(line))
               line += "[$i]";
         }
         else
         {
            int equal = line.find('=');
            if (equal != -1)
            {
               TQString key = line.left(equal).stripWhiteSpace();
               int pos = option.searchRev(key);
               if (pos != -1)
               {
                  key = key.left(pos);
                  TQString ext = option.cap(0);
                  if (status->m_lines.take(key))
                     addImmutable(ext);
                  line = key + ext + line.mid(equal);
               }
               else
               {
                  if (status->m_lines.take(key))
                     line = key + "[$i]" + line.mid(equal);
               }
            }
         }

         (*pTxtOut) << line << endl;
      }
      oldCfg.close();
   }
   
   // Create remaining groups that were marked as immutable
   TQDictIterator<int> it( status->m_lines );
   for( ; it.current(); ++it )
   {
      TQString group = it.currentKey();
      if ( it.current() )
         (*pTxtOut) << endl << group << "[$i]" << endl;
   }
   
   if (!newCfg.close())
   {
      kdWarning() << "Error writing" <<  path << endl;
      delete status;
      return true; // Continue
   }
      
   TQString installLocation = m_saveInstance->dirs()->saveLocation("config", TQString(), false) + filename;
   if (!install(path, installLocation))
   {
      m_immutableStatusCache.insert(filename, status); // Keep it around
      return false;
   }
   delete status;
   return true;
}

bool
KioskRun::flushConfigCache()
{
   while ( !m_saveConfigCache.isEmpty() )
   {
      TQDictIterator<TDEConfig> it( m_saveConfigCache );
      TQString file = it.currentKey();
      TDEConfig *config = it.current();
      bool dirty = config->isDirty();
      config->sync(); // Save
      if (!restoreImmutableStatus(file, dirty))
         return false;
      m_saveConfigCache.remove(file);
   }
   
   if (m_forceSycocaUpdate)
      forceSycocaUpdate();
   return true;
}

TDEConfig *
KioskRun::configFile(const TQString &filename)
{
   TDEConfig *config = m_saveConfigCache.find(filename);
   if (config)
      return config;

   kdDebug() << "KioskRun::configFile(" << filename << ") loading file" << endl;

   setupConfigEnv();
   
   TQString saveLocation = saveImmutableStatus(filename);
   config = new KSimpleConfig(saveLocation);
   m_saveConfigCache.insert(filename, config);
   
   return config;
}

void
KioskRun::makeMutable(bool bMutable)
{
   TDEConfig *config = configFile("kdeglobals");
   
   m_noRestrictions = bMutable;
   // Propagate to tdeinit
   dcopRef("tdelauncher", "tdelauncher").call("setLaunchEnv", 
   	TQCString("TDE_KIOSK_NO_RESTRICTIONS"), TQCString(m_noRestrictions ? "true" : ""));
   
   setConfigImmutable("kdeglobals", "KDE Action Restrictions", true);
}

TQStringList
KioskRun::newConfigFiles()
{
   setupConfigEnv();

   TQStringList exceptions;
   exceptions << "tdeconf_updaterc";
   
   TQStringList result;
   TQDir dir(m_configDir);
   dir.setFilter( TQDir::Files | TQDir::NoSymLinks );

   const TQFileInfoList *list = dir.entryInfoList();
   if (!list) return result;

   TQFileInfoListIterator it( *list );
   TQFileInfo *fi;
   while ( (fi = it.current()) != 0 )
   {
      TQString file = fi->fileName();
      if (!file.endsWith("~") && !exceptions.contains(file)) // Skip backup files & exceptions
        result.append(file);
      ++it;
   }
   return result;
}

void
KioskRun::mergeConfigFile(const TQString &filename)
{
   TDEConfig *saveCfg = configFile(filename);

   kdDebug() << "KioskRun::mergeConfigFile(" << (m_configDir + filename) << ")" << endl;
   KSimpleConfig newCfg(m_configDir + filename);
   
   TQStringList groups = newCfg.groupList();
   for(TQStringList::ConstIterator it = groups.begin();
       it != groups.end(); ++it)
   {
      saveCfg->setGroup(*it);
      TQMap<TQString, TQString> map = newCfg.entryMap(*it);
      for(TQMap<TQString, TQString>::Iterator it2 = map.begin();
          it2 != map.end(); ++it2)
      {
#ifdef DEBUG_ENTRIES
tqWarning("[%s] %s --> %s", (*it).latin1(), it2.key().latin1(), it2.data().latin1());
#endif
         saveCfg->writeEntry(it2.key(), it2.data());
      }
   }
}

bool
KioskRun::setupRuntimeEnv()
{
   if (m_dcopClient) return true;
   
   KioskRunProgressDialog dlg(kapp->mainWidget(), "kioskrun_progress",
                      i18n("Setting Up Configuration Environment"),
                      i18n("Setting up configuration environment."));

   char hostname[256];
   hostname[0] = 0;
   gethostname(hostname, 255);
   TQString cacheDir = TQString("%1/.trinity/cache-%2").arg(m_homeDir).arg(hostname);

   deleteDir(cacheDir);
   TDEStandardDirs::makeDir(cacheDir);
   deleteDir(m_homeDir+"/.qt");
   ::unlink(TQFile::encodeName(m_homeDir+".tderc"));

   TQString iceFileBase = "ICEauthority";
   TQString iceAuth;
   if (getenv("XDG_RUNTIME_DIR"))
   {
      iceAuth = TQString("%1/%2").arg(getenv("XDG_RUNTIME_DIR")).arg(iceFileBase);
      TQFileInfo fi(iceAuth);
      if (!fi.exists())
      {
         iceAuth = TQString::null;
      }
   }
   if (iceAuth.isEmpty())
   {
      iceAuth = TQString("%1/.%2").arg(TQDir::homeDirPath()).arg(iceFileBase);
   }
   setenv("ICEAUTHORITY", TQFile::encodeName(iceAuth), 0); // Don't overwrite existing setting

   TQString xAuth = TQString("%1/.Xauthority").arg(TQDir::homeDirPath());
   setenv("XAUTHORITY", TQFile::encodeName(xAuth), 0); // Don't overwrite existing setting

   TQString dcopServerFile = m_homeDir+"/.trinity/DCOPserver";

   TDEProcess tdeinit;
   
   applyEnvironment(&tdeinit);
   
   tdeinit << "tdeinit";
   
   connect(&tdeinit, TQ_SIGNAL(processExited(TDEProcess *)), &dlg, TQ_SLOT(slotFinished()));
   
   tdeinit.start(TDEProcess::NotifyOnExit);

   dlg.exec();

   TQCString dcopSrv;
   TQFile f(dcopServerFile);
   if (f.open(IO_ReadOnly))
   {
       int size = TQMIN( 1024, f.size() ); // protection against a huge file
       TQCString contents( size+1 );
       if ( f.readBlock( contents.data(), size ) == size )
       {
           contents[size] = '\0';
           int pos = contents.find('\n');
           if ( pos == -1 ) // Shouldn't happen
               dcopSrv = contents;
           else
               dcopSrv = contents.left( pos );
       }
   }

   if (dcopSrv.isEmpty())
   {
       kdWarning() << "Error reading " << dcopServerFile << endl;
       m_dcopClient = new DCOPClient;
       shutdownRuntimeEnv();
       return false;
   }

   m_dcopClient = new DCOPClient;
   m_dcopClient->setServerAddress(dcopSrv);
   unsetenv("DCOPSERVER"); // Don't propagate it
   m_dcopClient->attach();
   return true;
}

void
KioskRun::shutdownRuntimeEnv()
{
   if (!m_dcopClient) return;

   delete m_dcopClient;
   m_dcopClient = 0;
   
   TDEProcess tdeinit;
   applyEnvironment(&tdeinit);
   
   tdeinit << "tdeinit_shutdown";
   
   tdeinit.start(TDEProcess::Block);

   TDEProcess dcopserver;
   applyEnvironment(&dcopserver);
   
   dcopserver << "dcopserver_shutdown";
   
   dcopserver.start(TDEProcess::Block);
}

DCOPRef
KioskRun::dcopRef(const TQCString &appId, const TQCString &objId)
{
   if (!setupRuntimeEnv())
      return DCOPRef();
   DCOPRef ref(appId, objId);
   ref.setDCOPClient(m_dcopClient);
   return ref;
}

// Lookup the setting for a custom action
bool
KioskRun::lookupCustomAction(const TQString &action)
{
   TDEConfig *cfg = KioskRun::self()->configFile("kdeglobals");
   cfg->setGroup("KDE Custom Restrictions");
   return cfg->readBoolEntry(action, false);
}

// Change the setting for a custom action
void
KioskRun::setCustomAction(const TQString &action, bool checked)
{
   TDEConfig *cfg = KioskRun::self()->configFile("kdeglobals");
   cfg->setGroup("KDE Custom Restrictions");
   if (cfg->readBoolEntry(action, false) != checked)
   {
      cfg->writeEntry(action, checked);
      KioskRun::self()->scheduleSycocaUpdate();

      if (action == "restrict_file_browsing")
      {
         setCustomRestrictionFileBrowsing(checked);
      }
   }
}

// Create directory
bool
KioskRun::createDir(const TQString &dir)
{
   if (TQDir(dir).exists())
      return true; // Exists already

   KURL dest;
   if (!m_isRoot || (m_user != "root"))
   {
      dest.setProtocol("fish");
      dest.setHost("localhost");
      dest.setUser(m_user);
   }
   dest.setPath(dir);

   if (dir.length() > 1)
   {
      KURL parent = dest.upURL();
      
      bool result = createDir(parent.path());
      if (!result)
         return false;
   }

   do
   {
      if (NETACCESS::exists(dest, false, kapp->mainWidget()))
         return true;
   
      bool result = NETACCESS::mkdir(dest, kapp->mainWidget(), 0755);
      if (result == true)
         return true;
         
      TQString error = NETACCESS::lastErrorString();
      TQString msg;

      if (error.isEmpty())
         msg = i18n("<qt>The directory <b>%1</b> could not be created because of an unspecified problem.<p>")
                    .arg(dir);
      else
         msg = i18n("<qt>The directory <b>%1</b> could not be created because of the following problem:"
                    "<p>%2<p>")
                    .arg(dir, NETACCESS::lastErrorString());

      msg += i18n("Without this directory your changes can not be saved.<p>"
         "Do you want to retry creating the directory or abort the saving of changes?</qt>");
         
      int msgResult = KMessageBox::warningYesNo(kapp->mainWidget(), msg, TQString(),
                             i18n("&Retry"), i18n("&Abort"));

      if (msgResult == KMessageBox::No)
         return false;

      // Maybe the user created it in the meantime
      if (TQDir(dir).exists())
         return true; // Exists already
   }
   while (true);
   return false;
}

// Create directory
bool
KioskRun::createRemoteDirRecursive(const KURL &dest, bool ask)
{
   if (NETACCESS::exists(dest, false, kapp->mainWidget()))
      return true;

   KURL parent = dest.upURL();

   if (NETACCESS::exists(dest, false, kapp->mainWidget()))
   {
      return createRemoteDir(dest);
   }

   if (ask)
   {
      // Parent doesn't exist, 
      int result = KMessageBox::warningContinueCancel(kapp->mainWidget(),
                i18n("<qt>The directory <b>%1</b> does not yet exist. "
                     "Do you want to create it?").arg(parent.prettyURL()), TQString(), 
                i18n("Create &Dir"));
      if (result != KMessageBox::Continue)
         return false;
   }
  
   TQString path = dest.path(1);
   int i = 0;
   while ( (i = path.find('/', i+1)) != -1)
   {
      parent.setPath(path.left(i+1));
      if (! createRemoteDir(parent))
         return false;
   }
   return true;
}

// Create directory
bool
KioskRun::createRemoteDir(const KURL &dest)
{
   do
   {
      if (NETACCESS::exists(dest, false, kapp->mainWidget()))
         return true;

      if (NETACCESS::mkdir(dest, kapp->mainWidget(), 0755))
         return true;

      if (NETACCESS::lastError() == TDEIO::ERR_DIR_ALREADY_EXIST)
         return true;

      //TODO Check directory already exists error
      TQString error = NETACCESS::lastErrorString();
      TQString msg;

      if (error.isEmpty())
         msg = i18n("<qt>The directory <b>%1</b> could not be created because of an unspecified problem.<p>")
                    .arg(dest.prettyURL());
      else
         msg = i18n("<qt>The directory <b>%1</b> could not be created because of the following problem:"
                    "<p>%2<p>")
                    .arg(dest.prettyURL(), NETACCESS::lastErrorString());

      msg += i18n("Without this directory your files can not be uploaded.<p>"
         "Do you want to retry creating the directory or abort uploading?</qt>");
         
      int msgResult = KMessageBox::warningYesNo(kapp->mainWidget(), msg, TQString(),
                             i18n("&Retry"), i18n("&Abort"));

      if (msgResult == KMessageBox::No)
         return false;
   }
   while (true);
   return false;
}

// Install file
bool
KioskRun::install(const TQString &file, const TQString &destination)
{
   KURL dest;
   if (!m_isRoot || (m_user != "root"))
   {
      dest.setProtocol("fish");
      dest.setHost("localhost");
      dest.setUser(m_user);
   }
   dest.setPath(destination);

   if (!createDir(dest.upURL().path()))
      return false;

   do
   {
      KURL src;
      src.setPath(file);
      bool result = NETACCESS::file_copy(src, dest, 0644, true, false, kapp->mainWidget());
      if (result == true)
      {
         ::unlink(TQFile::encodeName(file));
         return true;
      }
         
      TQString error = NETACCESS::lastErrorString();
      TQString msg;
      if (error.isEmpty())
         msg = i18n("<qt>The file <b>%1</b> could not be installed because of an unspecified problem.")
                    .arg(destination);
      else
         msg = i18n("<qt>The file <b>%1</b> could not be installed because of the following problem:"
                    "<p>%2<p>")
                    .arg(destination, NETACCESS::lastErrorString());

      msg += i18n("Do you want to retry the installation or abort the saving of changes?</qt>");
         
      int msgResult = KMessageBox::warningYesNo(kapp->mainWidget(), msg, TQString(),
                             i18n("&Retry"), i18n("&Abort"));

      if (msgResult == KMessageBox::No)
         return false;
   }
   while (true);
   return false;
}

// Upload file
bool
KioskRun::uploadRemote(const TQString &file, const KURL &dest)
{
   do
   {
      KURL src;
      src.setPath(file);
      bool result = NETACCESS::file_copy(src, dest, 0644, true, false, kapp->mainWidget());
      if (result == true)
         return true;
         
      TQString error = NETACCESS::lastErrorString();
      TQString msg;
      if (error.isEmpty())
         msg = i18n("<qt>The file <b>%1</b> could not be uploaded to <b>%2</b> because of an unspecified problem.")
                    .arg(file, dest.prettyURL());
      else
         msg = i18n("<qt>The file <b>%1</b> could not be uploaded to <b>%2</b> because of the following problem:"
                    "<p>%3<p>")
                    .arg(file, dest.prettyURL(),NETACCESS::lastErrorString());

      msg += i18n("Do you want to retry or abort the uploading?</qt>");
         
      int msgResult = KMessageBox::warningYesNo(kapp->mainWidget(), msg, TQString(),
                             i18n("&Retry"), i18n("&Abort"));

      if (msgResult == KMessageBox::No)
         return false;
   }
   while (true);
   return false;
}

// Remove file
bool
KioskRun::remove(const TQString &destination)
{
   KURL dest;
   if (!m_isRoot || (m_user != "root"))
   {
      dest.setProtocol("fish");
      dest.setHost("localhost");
      dest.setUser(m_user);
   }
   dest.setPath(destination);

   return NETACCESS::del(dest, kapp->mainWidget());
}

// Move file or directory
bool
KioskRun::move(const TQString &source, const TQString &destination, const TQStringList &files)
{
   KURL src;
   KURL dest;
   if (!m_isRoot || (m_user != "root"))
   {
      dest.setProtocol("fish");
      dest.setHost("localhost");
      dest.setUser(m_user);
      src.setProtocol("fish");
      src.setHost("localhost");
      src.setUser(m_user);
   }

   for(TQStringList::ConstIterator it = files.begin();
       it != files.end(); ++it)
   {
      src.setPath(source + *it);
      dest.setPath(destination + *it);

kdDebug() << "Moving " << src << " --> " << dest << endl;   
      if (!createRemoteDirRecursive(dest.upURL(), false))
         return false;

      if (!NETACCESS::file_move(src, dest, -1, true, false, kapp->mainWidget()))
      {
         // TODO add error message + retry
         return false;
      }
   }
   return true;
}

// Read information of profile @p profile
void
KioskRun::getProfileInfo(const TQString &profile, TQString &description, TQString &installDir, TQString &installUser)
{
   TDEConfig *config = kapp->config();

   TQString defaultInstallDir = getProfilePrefix();
   if (defaultInstallDir.isEmpty())
   {
      defaultInstallDir = "/etc/kde-profile/";
   }
   if (!defaultInstallDir.endsWith("/"))
      defaultInstallDir.append("/");
   TQString tmp = profile;
   tmp.replace(" ", "_");
   tmp.replace(":", "_");
   tmp.replace("/", "_");
   defaultInstallDir += tmp+"/";

   TQString group = TQString("Directories-%1").arg(profile);
   config->setGroup(group);
   
   installDir = config->readEntry("prefixes", defaultInstallDir);
   if (!installDir.endsWith("/"))
      installDir.append("/");

   TQString profileInfoFile = installDir + ".kdeprofile";
   if (TQFile::exists(profileInfoFile))
   {
      KSimpleConfig profileInfo(profileInfoFile, true);
      description = profileInfo.readEntry("Description");
      installUser = profileInfo.readEntry("InstallUser", "root");
      return;
   }

   TQString defaultDescription;
   if (profile == "default")
      defaultDescription = i18n("Default profile");
   
   description = config->readEntry("ProfileDescription", defaultDescription);
   installUser = config->readEntry("ProfileInstallUser", "root");
}

KSimpleConfig *
KioskRun::openKderc()
{
   if (m_localKdercConfig)
      return m_localKdercConfig;
     
   KURL settingsUrl;
   settingsUrl.setPath(m_tderc);

   m_localKderc = ::locateLocal("tmp", "tderc_"+kapp->randomString(5));
   ::unlink(TQFile::encodeName(m_localKderc));

   KURL localCopyUrl;
   localCopyUrl.setPath(m_localKderc);

   if (TQFile::exists(settingsUrl.path()))
   {
   
      while (!NETACCESS::copy(settingsUrl, localCopyUrl, kapp->mainWidget()))
      {
         TQString error = NETACCESS::lastErrorString();
         TQString msg;
         if (error.isEmpty())
            msg = i18n("<qt>The file <b>%1</b> could not be accessed because of an unspecified problem.")
                             .arg(settingsUrl.path());
         else
            msg = i18n("<qt>The file <b>%1</b> could not be accessed because of the following problem:"
                            "<p>%2<p>")
                             .arg(settingsUrl.path(), error);

         msg += i18n("Do you want to retry the operation or abort the saving of changes?</qt>");
         
         int msgResult = KMessageBox::warningYesNo(kapp->mainWidget(), msg, TQString(),
                             i18n("&Retry"), i18n("&Abort"));

         if (msgResult == KMessageBox::No)
            return 0;
      }
   }
   
   m_localKdercConfig = new KSimpleConfig(m_localKderc);
   return m_localKdercConfig;
}

bool
KioskRun::closeKderc()
{
   if (!m_localKdercConfig)
      return false;
   m_localKdercConfig->sync();
   delete m_localKdercConfig;
   m_localKdercConfig = 0;

   TQString saveUser = m_user;
   m_user = "root";
   bool result = install(m_localKderc, m_tderc);
   m_localKderc = TQString();
   m_user = saveUser;
   kapp->config()->reparseConfiguration();
   return result;
}

// Store information for profile @p profile
bool
KioskRun::setProfileInfo(const TQString &profile, const TQString &description, const TQString &_installDir, const TQString &installUser, bool deleteProfile, bool deleteFiles)
{
   TQString installDir = _installDir;
   if (!installDir.endsWith("/"))
      installDir.append("/");

   TQString saveProfileInfo = installDir + ".kdeprofile";      
   KSimpleConfig profileInfo(saveProfileInfo, true);
   TQString oldDescription = profileInfo.readEntry("Description");
   TQString oldInstallUser = profileInfo.readEntry("InstallUser");
   if (deleteProfile && !installDir.isEmpty())
   {
      bool result = true;
      KioskSync profileDir(kapp->mainWidget());
      profileDir.addDir(installDir, KURL());
      TQStringList allFiles = profileDir.listFiles();
      allFiles.remove(".kdeprofile");
      if (allFiles.isEmpty())
      {
         if (TQDir(installDir).exists())
         {
            m_user = installUser;
            remove(installDir);
            m_user = TQString();
         }
      }
      else if (deleteFiles)
      {
         int msgResult = KMessageBox::warningYesNoCancelList(kapp->mainWidget(), 
                          i18n("<qt>The profile directory <b>%1</b> contains the following files, "
                               "do you wish to delete these files?").arg(installDir),
                          allFiles,
                          i18n("Deleting Profile"),
                          KStdGuiItem::del(),
                          i18n("&Keep Files")
                               );
         switch(msgResult)
         {
           case KMessageBox::Yes:
             // Delete files
             m_user = installUser;
             result = remove(installDir);
             m_user = TQString();
             if (!result)
                return false;
             break;
             
           case KMessageBox::No:
             // Keep files
             break;
             
           default:
             // Cancel
             return false;
         }
      }

      m_user = installUser;
      if (TQFile::exists(saveProfileInfo))
         result = remove(saveProfileInfo);
      m_user = TQString();
      if (!result)
         return false;
   }
   else if ((description != oldDescription) ||
       (installUser != oldInstallUser))
   {
      TQString localProfileInfo = ::locateLocal("tmp", "kdeprofile_"+kapp->randomString(5));
      ::unlink(TQFile::encodeName(localProfileInfo));
      KSimpleConfig newProfileInfo(localProfileInfo);
      newProfileInfo.writeEntry("Description", description);
      newProfileInfo.writeEntry("InstallUser", installUser);
      newProfileInfo.sync();
      bool result = install(localProfileInfo, saveProfileInfo);
      if (!result)
         return false;
   }

   KUser thisUser;
   TQString newAdmin = thisUser.loginName()+":"; // This user, all hosts

   TDEConfig *config = kapp->config();

   config->setGroup("Directories");
   TQString oldAdmin = config->readEntry("kioskAdmin");
   
   TQString group = TQString("Directories-%1").arg(profile);
   config->setGroup(group);
  
   if ((installDir == config->readEntry("prefixes")) &&
       (newAdmin == oldAdmin) &&
       !deleteProfile)
      return true; // Nothing to do

   KSimpleConfig *cfg = openKderc();
   if (!cfg)
      return false;

   cfg->setGroup("Directories");
   cfg->writeEntry("kioskAdmin", newAdmin);

   if (deleteProfile)
   {
      cfg->deleteGroup(group);
   }
   else
   {   
      cfg->setGroup(group);
      // TODO: update prefixes
      cfg->writeEntry("prefixes", installDir);
   }
   cfg->sync();

   return closeKderc();
}

bool
KioskRun::deleteProfile(const TQString &profile, bool deleteFiles)
{
   TQString description;
   TQString installDir;
   TQString installUser;
   getProfileInfo(profile, description, installDir, installUser);
   return setProfileInfo(profile, description, installDir, installUser, true, deleteFiles);
}

// Read profile prefix
TQString
KioskRun::getProfilePrefix()
{
   TDEConfig *config = kapp->config();
      
   config->setGroup("Directories");
   
   TQString prefix = config->readEntry("profileDirsPrefix");
   if (!prefix.isEmpty() && !prefix.endsWith("/"))
      prefix.append('/');
   return prefix;
}
  
// Store profile prefix
bool
KioskRun::setProfilePrefix(const TQString &_prefix)
{
   TQString prefix = _prefix;
   
   if (!prefix.isEmpty() && !prefix.endsWith("/"))
      prefix.append('/');
            
   if (prefix == getProfilePrefix())
      return true; // Nothing to do
      
   KSimpleConfig *cfg = openKderc();
   if (!cfg)
      return false;

   cfg->setGroup("Directories");
   cfg->writeEntry("profileDirsPrefix", prefix);

   cfg->sync();

   return closeKderc();
}

TQString
KioskRun::newProfile()
{
   TQString profilePrefix = getProfilePrefix();

   TDEConfig *config = kapp->config();
   for(int p = 1; p; p++)
   {
      TQString profile = TQString("profile%1").arg(p);
      TQString group = TQString("Directories-%1").arg(profile);
      if (!config->hasGroup(group))
      {
         if (profilePrefix.isEmpty())
            return profile;

         TQString profileDir = profilePrefix + profile;
         if (!TQDir(profileDir).exists() && !TQFile::exists(profileDir))
            return profile;

         // Keep on looking...
      }
   }
   return TQString();
}

TQStringList
KioskRun::allProfiles()
{
   TDEConfig *config = kapp->config();
   TQStringList groups = config->groupList();
   TQStringList profiles;
   TQStringList directories;
   for(TQStringList::ConstIterator it = groups.begin();
       it != groups.end(); ++it)
   {
      if (!(*it).startsWith("Directories-"))
         continue;
      profiles.append((*it).mid(12));
      config->setGroup(*it);
      TQString installDir = config->readEntry("prefixes");
      if (!installDir.endsWith("/"))
         installDir.append("/");
      directories.append(installDir);
   }
      
   TQString profilePrefix = getProfilePrefix();
   if (!profilePrefix.isEmpty())
   {
      TQDir dir(profilePrefix, TQString(), TQDir::Unsorted, TQDir::Dirs);
      TQStringList profileDirs = dir.entryList();
      for(TQStringList::ConstIterator it = profileDirs.begin();
         it != profileDirs.end(); ++it)
      {
         if ((*it).startsWith("."))
            continue;
         TQString dir = profilePrefix + *it + "/";
         if (directories.contains(dir))
         {
            kdDebug() << "Skipping " << dir << ", dir already listed" << endl;
            continue;
         }
         if (profiles.contains(*it))
         {
            kdDebug() << "Skipping " << dir << ", profile [" << (*it) << "] already listed" << endl;
            continue;
         }
         
         if (!TQFile::exists(dir+".kdeprofile"))
         {
            kdDebug() << "Skipping " << dir << ", no profile info" << endl;
            continue;
         }
         profiles.append(*it);
         directories.append(dir);
      }
   }

   if (!profiles.contains("default"))
      profiles.append("default");
      
   return profiles;
}

void
KioskRun::getUserProfileMappings( ProfileMapping &groups, ProfileMapping &users, TQStringList &groupOrder)
{
   groups.clear();
   users.clear();
   
   TDEConfig *config = kapp->config();
   config->setGroup("Directories");
   TQString mapFile = config->readEntry("userProfileMapFile");
   
   if (mapFile.isEmpty() || !TQFile::exists(mapFile))
      return;
      
   KSimpleConfig mapCfg(mapFile, true);

   mapCfg.setGroup("General");
   groupOrder = mapCfg.readListEntry("groups");
        
   mapCfg.setGroup("Groups");
   for ( TQStringList::ConstIterator it = groupOrder.begin(); 
         it != groupOrder.end(); ++it )
   {
      TQString group = *it;
      TQStringList profiles = mapCfg.readListEntry(group);
      if (!profiles.isEmpty())
         groups.insert(group, profiles);
   }

   TQMap<TQString, TQString> cfg_users = mapCfg.entryMap("Users");
   for ( TQMap<TQString, TQString>::Iterator it = cfg_users.begin();
         it != cfg_users.end(); ++it )
   {
      TQString user = it.key();
      TQStringList profiles = TQStringList::split(",", it.data());
      if (!profiles.isEmpty())
         users.insert(user, profiles);
   }
}

bool
KioskRun::setUserProfileMappings( const ProfileMapping &groups, const ProfileMapping &users, const TQStringList &groupOrder)
{
   TDEConfig *config = kapp->config();
   config->setGroup("Directories");
   TQString mapFile = config->readEntry("userProfileMapFile");
   if (mapFile.isEmpty())
   {
     mapFile = "/etc/kde-user-profile";
     
     KSimpleConfig *cfg = openKderc();
     if (!cfg)
        return false;
     
     cfg->setGroup("Directories");
     cfg->writeEntry("userProfileMapFile", mapFile);
     if (!closeKderc())
        return false;
   }
   
   TQString localMapFile = ::locateLocal("tmp", "kde-user-profile_"+kapp->randomString(5));
   ::unlink(TQFile::encodeName(localMapFile));

   KSimpleConfig mapConfig(localMapFile);

   mapConfig.setGroup("General");
   mapConfig.writeEntry("groups", groupOrder);

   KioskRun::ProfileMapping::ConstIterator it;
   
   mapConfig.setGroup("Groups");
   for ( it = groups.begin(); it != groups.end(); ++it )
   {
      TQString group = it.key();
      mapConfig.writeEntry(group, it.data());
   }
   mapConfig.setGroup("Users");
   for ( it = users.begin(); it != users.end(); ++it )
   {
      TQString user = it.key();
      mapConfig.writeEntry(user, it.data());
   }
                                 
   mapConfig.sync();
   
   TQString saveUser = m_user;
   m_user = "root";
   bool result = install(localMapFile, mapFile);
   m_user = saveUser;
   return result;
}

void
KioskRun::forceSycocaUpdate()
{
   // Touch $TDEDIR/share/services/update_tdesycoca
   KTempFile tempFile;
   tempFile.close();
   TQString sycocaUpdateFile = locateSave("services", "update_tdesycoca");
   remove(sycocaUpdateFile);
   install(tempFile.name(), sycocaUpdateFile);
}

void
KioskRun::scheduleSycocaUpdate()
{
   m_forceSycocaUpdate = true;
}

void
KioskRun::setCustomRestrictionFileBrowsing(bool restrict)
{
   TQString file = "kdeglobals";
   TQString group = "KDE URL Restrictions";
   TDEConfig *cfg = KioskRun::self()->configFile(file);
   cfg->setGroup(group);
   int count = cfg->readNumEntry("rule_count");
   TQStringList urlRestrictions;
   for(int i = 0; i < count; i++)
   {
      TQString key = TQString("rule_%1").arg(i+1);
      if (cfg->hasKey(key))
         urlRestrictions.append(cfg->readEntry(key));
   }
   
   TQStringList newRestrictions;
   newRestrictions << "list,,,,file,,,false";
   newRestrictions << "list,,,,file,,$HOME,true";
   
   for(TQStringList::ConstIterator it = newRestrictions.begin();
       it != newRestrictions.end(); ++it)
   {
      urlRestrictions.remove(*it);
   }
   
   if (restrict)
   {
      newRestrictions += urlRestrictions;
      urlRestrictions = newRestrictions;
   }
   
   count = urlRestrictions.count();
   cfg->writeEntry("rule_count", count);

   for(int i = 0; i < count; i++)
   {
      TQString key = TQString("rule_%1").arg(i+1);
      cfg->writeEntry(key, urlRestrictions[i]);
   }
   KioskRun::self()->setConfigImmutable(file, group, true);
}

KioskRunProgressDialog::KioskRunProgressDialog(TQWidget *parent, const char *name,
                          const TQString &caption, const TQString &text)
 : KProgressDialog(parent, name, caption, text, true)
{
  connect(&m_timer, TQ_SIGNAL(timeout()), this, TQ_SLOT(slotProgress()));
  progressBar()->setTotalSteps(20);
  m_timeStep = 700;
  m_timer.start(m_timeStep);
  setAutoClose(false);
}

void
KioskRunProgressDialog::slotProgress()
{
  int p = progressBar()->progress();
  if (p == 18)
  {
     progressBar()->reset();
     progressBar()->setProgress(1);
     m_timeStep = m_timeStep * 2;
     m_timer.start(m_timeStep);
  }
  else
  {
     progressBar()->setProgress(p+1);
  }
}

void
KioskRunProgressDialog::slotFinished()
{
  progressBar()->setProgress(20);
  m_timer.stop();
  TQTimer::singleShot(1000, this, TQ_SLOT(close()));
}


#include "kioskrun.moc"

