/*
 *   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 <qdir.h>
#include <qfile.h>

#include <kapplication.h>
#include <kcmdlineargs.h>
#include <kconfig.h>
#include <kdebug.h>
#include <klocale.h>
#include <kmessagebox.h>
#include <kprocess.h>
#include <ksavefile.h>
#include <ksimpleconfig.h>
#include <kstandarddirs.h>
#include <kurl.h>
#include <kuser.h>

#include "kiosksync.h"

#include <kio/netaccess.h>
#define NETACCESS	KIO::NetAccess

#undef DEBUG_ENTRIES

KioskRun *KioskRun::s_self = 0;

KioskRun::KioskRun( QObject* parent, const char* name)
 : QObject(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 = QDir::homeDirPath()+"/.kde-test";
   KCmdLineArgs *args = KCmdLineArgs::parsedArgs();
   m_kderc = QFile::decodeName(args->getOption("kderc"));
   m_isRoot = (getuid() == 0);
}

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

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

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

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

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

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

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

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

void
KioskRun::applyEnvironment(KProcess *p)
{
   p->setEnvironment("HOME", m_homeDir);
   p->setEnvironment("KDEHOME", m_homeDir+"/.kde");
   p->setEnvironment("KDEROOTHOME", m_homeDir+"/.kde");
   p->setEnvironment("KDEDIRS", 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+"/.kde/DCOPserver");
   p->setEnvironment("KDE_KIOSK_NO_PROFILES", "true");
   if (m_noRestrictions)
      p->setEnvironment("KDE_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
   QString sycocaUpdateFile = KioskRun::self()->locateLocal("services", "update_ksycoca");
   QFile file(sycocaUpdateFile);
   file.remove();
   file.open(IO_WriteOnly);
   file.close();

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

KProcess*
KioskRun::run(const QString &cmd, const QStringList &args)
{
   KProcess *proc = new KProcess(this);
   
   applyEnvironment(proc);

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

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

class SetEnv
{
public:
   SetEnv(const char *key, const QString &value) : m_key(key)
   {
      m_oldValue = getenv(m_key);
      setenv(m_key, QFile::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;
   QCString m_oldValue;
};

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

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

   SetEnv home("HOME", m_homeDir);
   QString kdeHome = m_homeDir+"/.kde";
   SetEnv kdehome("KDEHOME", kdeHome);
   SetEnv kderoothome("KDEROOTHOME", kdeHome);
   SetEnv kdedirs("KDEDIRS", 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(QFile::encodeName(m_homeDir), 0700);
   ::mkdir(QFile::encodeName(kdeHome), 0700);

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

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

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

   m_configDir = QString("%1/.kde/share/config/").arg(m_homeDir);

   m_instance = new KInstance("kioskrun");
   (void) m_instance->dirs(); // Create KStandardDirs obj

   m_desktopPath = m_homeDir + "/Desktop/";
   m_desktopPath = m_instance->config()->readPathEntry( "Desktop", m_desktopPath);
   m_desktopPath = QDir::cleanDirPath( m_desktopPath );
   if ( !m_desktopPath.endsWith("/") )
      m_desktopPath.append('/');
   
   {
      SetEnv kdehome("KDEHOME", "-");
      SetEnv kderoothome("KDEROOTHOME", "-");
      SetEnv xdgDataHome("XDG_DATA_HOME", m_xdgDataDirs.first());
      SetEnv xdgConfigHome("XDG_CONFIG_HOME", m_xdgConfigDirs.first());

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

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

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

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

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

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

   // split path from filename
   int slash = filename.findRev('/')+1;
   QString dir = filename.left(slash);
   QString 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;
   QDict<int> m_lines;
   QString m_tmpFile;
   bool m_dirty;
};


bool
KioskRun::isConfigImmutable(const QString &filename, const QString &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 QString &filename, const QString &_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
   {
      QString group = QString("[%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(QString &ext)
{
   ext.replace("i", "");
   if (ext == "[$]")
      ext = QString::null;
}

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

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

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

   while(! txtIn.atEnd())
   {
      QString line = txtIn.readLine().stripWhiteSpace();
      
      if (line.startsWith("#"))
      {
        // Comment, do nothing...
      }
      else if (line.startsWith("["))
      {
        int pos = immutable.searchRev(line);
        if (pos != -1)
        {
           QString group = line.left(pos);
           QString 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)
        {
          QString key = line.left(equal).stripWhiteSpace();
          int pos = immutable.searchRev(key);
          if (pos != -1)
          {
             key = key.left(pos);
             QString 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 QString &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;

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

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

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

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

      QTextStream txtIn(&oldCfg);
      txtIn.setEncoding(QTextStream::UnicodeUTF8);
   
      while(! txtIn.atEnd())
      {
         QString 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)
            {
               QString key = line.left(equal).stripWhiteSpace();
               int pos = option.searchRev(key);
               if (pos != -1)
               {
                  key = key.left(pos);
                  QString 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
   QDictIterator<int> it( status->m_lines );
   for( ; it.current(); ++it )
   {
      QString group = it.currentKey();
      if ( it.current() )
         (*pTxtOut) << endl << group << "[$i]" << endl;
   }
   
   if (!newCfg.close())
   {
      kdWarning() << "Error writing" <<  path << endl;
      delete status;
      return true; // Continue
   }
      
   QString installLocation = m_saveInstance->dirs()->saveLocation("config", QString::null, 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() )
   {
      QDictIterator<KConfig> it( m_saveConfigCache );
      QString file = it.currentKey();
      KConfig *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;
}

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

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

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

void
KioskRun::makeMutable(bool bMutable)
{
   KConfig *config = configFile("kdeglobals");
   
   m_noRestrictions = bMutable;
   if (KDE::version() < KDE_MAKE_VERSION(3,2,4))
   {
      config->setGroup("KDE Action Restrictions");
      if (bMutable)
      {
         KUser thisUser;
         config->writeEntry("kiosk_exception", thisUser.loginName()+":"); // This user, all hosts
      }
      else
      {
         config->writeEntry("kiosk_exception", QString::null);
      }
   }
   // Propagate to kdeinit
   dcopRef("klauncher", "klauncher").call("setLaunchEnv", 
   	QCString("KDE_KIOSK_NO_RESTRICTIONS"), QCString(m_noRestrictions ? "true" : ""));
   
   setConfigImmutable("kdeglobals", "KDE Action Restrictions", true);
}

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

   QStringList exceptions;
   exceptions << "kconf_updaterc";
   
   QStringList result;
   QDir dir(m_configDir);
   dir.setFilter( QDir::Files | QDir::NoSymLinks );

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

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

void
KioskRun::mergeConfigFile(const QString &filename)
{
   KConfig *saveCfg = configFile(filename);

   kdDebug() << "KioskRun::mergeConfigFile(" << (m_configDir + filename) << ")" << endl;
   KSimpleConfig newCfg(m_configDir + filename);
   
   QStringList groups = newCfg.groupList();
   for(QStringList::ConstIterator it = groups.begin();
       it != groups.end(); ++it)
   {
      saveCfg->setGroup(*it);
      QMap<QString, QString> map = newCfg.entryMap(*it);
      for(QMap<QString, QString>::Iterator it2 = map.begin();
          it2 != map.end(); ++it2)
      {
#ifdef DEBUG_ENTRIES
qWarning("[%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);
   QString cacheDir = QString("%1/.kde/cache-%2").arg(m_homeDir).arg(hostname);

   deleteDir(cacheDir);
   KStandardDirs::makeDir(cacheDir);
   deleteDir(m_homeDir+"/.qt");
   ::unlink(QFile::encodeName(m_homeDir+".kderc"));
   

   QString iceAuth = QString("%1/.ICEauthority").arg(QDir::homeDirPath());
   setenv("ICEAUTHORITY", QFile::encodeName(iceAuth), 0); // Don't overwrite existing setting

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

   QString dcopServerFile = m_homeDir+"/.kde/DCOPserver";

   KProcess kdeinit;
   
   applyEnvironment(&kdeinit);
   
   kdeinit << "kdeinit";
   
   connect(&kdeinit, SIGNAL(processExited(KProcess *)), &dlg, SLOT(slotFinished()));
   
   kdeinit.start(KProcess::NotifyOnExit);

   dlg.exec();

   QCString dcopSrv;
   QFile f(dcopServerFile);
   if (f.open(IO_ReadOnly))
   {
       int size = QMIN( 1024, f.size() ); // protection against a huge file
       QCString 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;
   
   KProcess kdeinit;
   applyEnvironment(&kdeinit);
   
   kdeinit << "kdeinit_shutdown";
   
   kdeinit.start(KProcess::Block);

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

DCOPRef
KioskRun::dcopRef(const QCString &appId, const QCString &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 QString &action)
{
   KConfig *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 QString &action, bool checked)
{
   KConfig *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 QString &dir)
{
   if (QDir(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;
         
      QString error = NETACCESS::lastErrorString();
      QString 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, QString::null,
                             i18n("&Retry"), i18n("&Abort"));

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

      // Maybe the user created it in the meantime
      if (QDir(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()), QString::null, 
                i18n("Create &Dir"));
      if (result != KMessageBox::Continue)
         return false;
   }
  
   QString 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 KDE_IS_VERSION(3,2,91)
      if (NETACCESS::lastError() == KIO::ERR_DIR_ALREADY_EXIST)
         return true;
#endif

      //TODO Check directory already exists error
      QString error = NETACCESS::lastErrorString();
      QString 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, QString::null,
                             i18n("&Retry"), i18n("&Abort"));

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

// Install file
bool
KioskRun::install(const QString &file, const QString &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(QFile::encodeName(file));
         return true;
      }
         
      QString error = NETACCESS::lastErrorString();
      QString 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, QString::null,
                             i18n("&Retry"), i18n("&Abort"));

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

// Upload file
bool
KioskRun::uploadRemote(const QString &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;
         
      QString error = NETACCESS::lastErrorString();
      QString 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, QString::null,
                             i18n("&Retry"), i18n("&Abort"));

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

// Remove file
bool
KioskRun::remove(const QString &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 QString &source, const QString &destination, const QStringList &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(QStringList::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 QString &profile, QString &description, QString &installDir, QString &installUser)
{
   KConfig *config = kapp->config();

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

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

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

   QString 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_kderc);

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

   KURL localCopyUrl;
   localCopyUrl.setPath(m_localKderc);

   if (QFile::exists(settingsUrl.path()))
   {
   
      while (!NETACCESS::copy(settingsUrl, localCopyUrl, kapp->mainWidget()))
      {
         QString error = NETACCESS::lastErrorString();
         QString 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, QString::null,
                             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;

   QString saveUser = m_user;
   m_user = "root";
   bool result = install(m_localKderc, m_kderc);
   m_localKderc = QString::null;
   m_user = saveUser;
   kapp->config()->reparseConfiguration();
   return result;
}

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

   QString saveProfileInfo = installDir + ".kdeprofile";      
   KSimpleConfig profileInfo(saveProfileInfo, true);
   QString oldDescription = profileInfo.readEntry("Description");
   QString oldInstallUser = profileInfo.readEntry("InstallUser");
   if (deleteProfile && !installDir.isEmpty())
   {
      bool result = true;
      KioskSync profileDir(kapp->mainWidget());
      profileDir.addDir(installDir, KURL());
      QStringList allFiles = profileDir.listFiles();
      allFiles.remove(".kdeprofile");
      if (allFiles.isEmpty())
      {
         if (QDir(installDir).exists())
         {
            m_user = installUser;
            remove(installDir);
            m_user = QString::null;
         }
      }
      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"),
#if KDE_IS_VERSION(3,2,91)
                          KStdGuiItem::del(),
#else
                          i18n("&Delete"),
#endif                          
                          i18n("&Keep Files")
                               );
         switch(msgResult)
         {
           case KMessageBox::Yes:
             // Delete files
             m_user = installUser;
             result = remove(installDir);
             m_user = QString::null;
             if (!result)
                return false;
             break;
             
           case KMessageBox::No:
             // Keep files
             break;
             
           default:
             // Cancel
             return false;
         }
      }

      m_user = installUser;
      if (QFile::exists(saveProfileInfo))
         result = remove(saveProfileInfo);
      m_user = QString::null;
      if (!result)
         return false;
   }
   else if ((description != oldDescription) ||
       (installUser != oldInstallUser))
   {
      QString localProfileInfo = ::locateLocal("tmp", "kdeprofile_"+kapp->randomString(5));
      ::unlink(QFile::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;
   QString newAdmin = thisUser.loginName()+":"; // This user, all hosts

   KConfig *config = kapp->config();

   config->setGroup("Directories");
   QString oldAdmin = config->readEntry("kioskAdmin");
   
   QString group = QString("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 QString &profile, bool deleteFiles)
{
   QString description;
   QString installDir;
   QString installUser;
   getProfileInfo(profile, description, installDir, installUser);
   return setProfileInfo(profile, description, installDir, installUser, true, deleteFiles);
}

// Read profile prefix
QString
KioskRun::getProfilePrefix()
{
   KConfig *config = kapp->config();
      
   config->setGroup("Directories");
   
   QString prefix = config->readEntry("profileDirsPrefix");
   if (!prefix.isEmpty() && !prefix.endsWith("/"))
      prefix.append('/');
   return prefix;
}
  
// Store profile prefix
bool
KioskRun::setProfilePrefix(const QString &_prefix)
{
   QString 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();
}

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

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

         QString profileDir = profilePrefix + profile;
         if (!QDir(profileDir).exists() && !QFile::exists(profileDir))
            return profile;

         // Keep on looking...
      }
   }
   return QString::null;
}

QStringList
KioskRun::allProfiles()
{
   KConfig *config = kapp->config();
   QStringList groups = config->groupList();
   QStringList profiles;
   QStringList directories;
   for(QStringList::ConstIterator it = groups.begin();
       it != groups.end(); ++it)
   {
      if (!(*it).startsWith("Directories-"))
         continue;
      profiles.append((*it).mid(12));
      config->setGroup(*it);
      QString installDir = config->readEntry("prefixes");
      if (!installDir.endsWith("/"))
         installDir.append("/");
      directories.append(installDir);
   }
      
   QString profilePrefix = getProfilePrefix();
   if (!profilePrefix.isEmpty())
   {
      QDir dir(profilePrefix, QString::null, QDir::Unsorted, QDir::Dirs);
      QStringList profileDirs = dir.entryList();
      for(QStringList::ConstIterator it = profileDirs.begin();
         it != profileDirs.end(); ++it)
      {
         if ((*it).startsWith("."))
            continue;
         QString 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 (!QFile::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, QStringList &groupOrder)
{
   groups.clear();
   users.clear();
   
   KConfig *config = kapp->config();
   config->setGroup("Directories");
   QString mapFile = config->readEntry("userProfileMapFile");
   
   if (mapFile.isEmpty() || !QFile::exists(mapFile))
      return;
      
   KSimpleConfig mapCfg(mapFile, true);

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

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

bool
KioskRun::setUserProfileMappings( const ProfileMapping &groups, const ProfileMapping &users, const QStringList &groupOrder)
{
   KConfig *config = kapp->config();
   config->setGroup("Directories");
   QString 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;
   }
   
   QString localMapFile = ::locateLocal("tmp", "kde-user-profile_"+kapp->randomString(5));
   ::unlink(QFile::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 )
   {
      QString group = it.key();
      mapConfig.writeEntry(group, it.data());
   }
   mapConfig.setGroup("Users");
   for ( it = users.begin(); it != users.end(); ++it )
   {
      QString user = it.key();
      mapConfig.writeEntry(user, it.data());
   }
                                 
   mapConfig.sync();
   
   QString saveUser = m_user;
   m_user = "root";
   bool result = install(localMapFile, mapFile);
   m_user = saveUser;
   return result;
}

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

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

void
KioskRun::setCustomRestrictionFileBrowsing(bool restrict)
{
   QString file = "kdeglobals";
   QString group = "KDE URL Restrictions";
   KConfig *cfg = KioskRun::self()->configFile(file);
   cfg->setGroup(group);
   int count = cfg->readNumEntry("rule_count");
   QStringList urlRestrictions;
   for(int i = 0; i < count; i++)
   {
      QString key = QString("rule_%1").arg(i+1);
      if (cfg->hasKey(key))
         urlRestrictions.append(cfg->readEntry(key));
   }
   
   QStringList newRestrictions;
   newRestrictions << "list,,,,file,,,false";
   newRestrictions << "list,,,,file,,$HOME,true";
   
   for(QStringList::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++)
   {
      QString key = QString("rule_%1").arg(i+1);
      cfg->writeEntry(key, urlRestrictions[i]);
   }
   KioskRun::self()->setConfigImmutable(file, group, true);
}

KioskRunProgressDialog::KioskRunProgressDialog(QWidget *parent, const char *name,
                          const QString &caption, const QString &text)
 : KProgressDialog(parent, name, caption, text, true)
{
  connect(&m_timer, SIGNAL(timeout()), this, 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();
  QTimer::singleShot(1000, this, SLOT(close()));
}


#include "kioskrun.moc"

