diff options
Diffstat (limited to 'src/newstuff/manager.cpp')
-rw-r--r-- | src/newstuff/manager.cpp | 446 |
1 files changed, 446 insertions, 0 deletions
diff --git a/src/newstuff/manager.cpp b/src/newstuff/manager.cpp new file mode 100644 index 0000000..3b7efbf --- /dev/null +++ b/src/newstuff/manager.cpp @@ -0,0 +1,446 @@ +/*************************************************************************** + copyright : (C) 2006 by Robby Stephenson + email : [email protected] + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of version 2 of the GNU General Public License as * + * published by the Free Software Foundation; * + * * + ***************************************************************************/ + +#include "manager.h" +#include "newscript.h" +#include "../filehandler.h" +#include "../tellico_debug.h" +#include "../tellico_utils.h" +#include "../tellico_kernel.h" +#include "../fetch/fetch.h" + +#include <kurl.h> +#include <ktar.h> +#include <kglobal.h> +#include <kio/netaccess.h> +#include <kconfig.h> +#include <ktempfile.h> +#include <kio/job.h> +#include <kfileitem.h> +#include <kdeversion.h> +#include <knewstuff/entry.h> +#include <kstandarddirs.h> + +#include <qfileinfo.h> +#include <qdir.h> +#include <qptrstack.h> +#include <qvaluestack.h> +#include <qwidget.h> + +#include <sys/types.h> +#include <sys/stat.h> + +using Tellico::NewStuff::Manager; + +Manager::Manager(QObject* parent_) : QObject(parent_), m_tempFile(0) { + m_infoList.setAutoDelete(true); +} + +Manager::~Manager() { + delete m_tempFile; + m_tempFile = 0; +} + +bool Manager::installTemplate(const KURL& url_, const QString& entryName_) { + FileHandler::FileRef* ref = FileHandler::fileRef(url_); + + QString xslFile; + QStringList allFiles; + + bool success = true; + + // is there a better way to figure out if the url points to a XSL file or a tar archive + // than just trying to open it? + KTar archive(ref->fileName()); + if(archive.open(IO_ReadOnly)) { + const KArchiveDirectory* archiveDir = archive.directory(); + archiveDir->copyTo(Tellico::saveLocation(QString::fromLatin1("entry-templates/"))); + + allFiles = archiveFiles(archiveDir); + // remember files installed for template + xslFile = findXSL(archiveDir); + } else { // assume it's an xsl file + QString name = entryName_; + if(name.isEmpty()) { + name = url_.fileName(); + } + if(!name.endsWith(QString::fromLatin1(".xsl"))) { + name += QString::fromLatin1(".xsl"); + } + KURL dest; + dest.setPath(Tellico::saveLocation(QString::fromLatin1("entry-templates/")) + name); + success = true; + if(QFile::exists(dest.path())) { + myDebug() << "Manager::installTemplate() - " << dest.path() << " exists!" << endl; + success = false; + } else if(KIO::NetAccess::file_copy(url_, dest)) { + xslFile = dest.fileName(); + allFiles += xslFile; + } + } + + if(xslFile.isEmpty()) { + success = false; + } else { + // remove ".xsl" + xslFile.truncate(xslFile.length()-4); + KConfigGroup config(KGlobal::config(), "KNewStuffFiles"); + config.writeEntry(xslFile, allFiles); + m_urlNameMap.insert(url_, xslFile); + } + + checkCommonFile(); + + delete ref; + return success; +} + +bool Manager::removeTemplate(const QString& name_) { + KConfigGroup fileGroup(KGlobal::config(), "KNewStuffFiles"); + QStringList files = fileGroup.readListEntry(name_); + // at least, delete xsl file + if(files.isEmpty()) { + kdWarning() << "Manager::deleteTemplate() no file list found for " << name_ << endl; + files += name_; + } + + bool success = true; + QString path = Tellico::saveLocation(QString::fromLatin1("entry-templates/")); + for(QStringList::ConstIterator it = files.begin(); it != files.end(); ++it) { + if((*it).endsWith(QChar('/'))) { + // ok to not delete all directories + QDir().rmdir(path + *it); + } else { + success = success && QFile(path + *it).remove(); + if(!success) { + myDebug() << "Manager::removeTemplate() - failed to remove " << (path+*it) << endl; + } + } + } + + // remove config entries even if unsuccessful + fileGroup.deleteEntry(name_); + KConfigGroup statusGroup(KGlobal::config(), "KNewStuffStatus"); + QString entryName = statusGroup.readEntry(name_); + statusGroup.deleteEntry(name_); + statusGroup.deleteEntry(entryName); + + return success; +} + +bool Manager::installScript(const KURL& url_) { + FileHandler::FileRef* ref = FileHandler::fileRef(url_); + + KTar archive(ref->fileName()); + if(!archive.open(IO_ReadOnly)) { + myDebug() << "Manager::installScript() - can't open tar file" << endl; + return false; + } + + const KArchiveDirectory* archiveDir = archive.directory(); + + QString exeFile = findEXE(archiveDir); + if(exeFile.isEmpty()) { + myDebug() << "Manager::installScript() - no exe file found" << endl; + return false; + } + + QFileInfo exeInfo(exeFile); + DataSourceInfo* info = new DataSourceInfo(); + + QString copyTarget = Tellico::saveLocation(QString::fromLatin1("data-sources/")); + QString scriptFolder; + + // package could have a top-level directory or not + // it should have a directory... + const KArchiveEntry* firstEntry = archiveDir->entry(archiveDir->entries().first()); + if(firstEntry->isFile()) { + copyTarget += exeInfo.baseName(true) + '/'; + if(QFile::exists(copyTarget)) { + KURL u; + u.setPath(scriptFolder); + myLog() << "Manager::installScript() - deleting " << scriptFolder << endl; + KIO::NetAccess::del(u, Kernel::self()->widget()); + info->isUpdate = true; + } + scriptFolder = copyTarget; + } else { + scriptFolder = copyTarget + firstEntry->name() + '/'; + if(QFile::exists(copyTarget + exeFile)) { + info->isUpdate = true; + } + } + + // overwrites stuff there + archiveDir->copyTo(copyTarget); + + info->specFile = scriptFolder + exeInfo.baseName(true) + ".spec"; + if(!QFile::exists(info->specFile)) { + myDebug() << "Manager::installScript() - no spec file for script! " << info->specFile << endl; + delete info; + return false; + } + info->sourceName = exeFile; + info->sourceExec = copyTarget + exeFile; + m_infoList.append(info); + + KURL dest; + dest.setPath(info->sourceExec); + KFileItem item(KFileItem::Unknown, KFileItem::Unknown, dest, true); + ::chmod(QFile::encodeName(dest.path()), item.permissions() | S_IXUSR); + + { + KConfig spec(info->specFile, false, false); + // update name + info->sourceName = spec.readEntry("Name", info->sourceName); + spec.writePathEntry("ExecPath", info->sourceExec); + spec.writeEntry("NewStuffName", info->sourceName); + spec.writeEntry("DeleteOnRemove", true); + } + + { + KConfigGroup config(KGlobal::config(), "KNewStuffFiles"); + config.writeEntry(info->sourceName, archiveFiles(archiveDir)); + m_urlNameMap.insert(url_, info->sourceName); + } + + +// myDebug() << "Manager::installScript() - exeFile = " << exeFile << endl; +// myDebug() << "Manager::installScript() - sourceExec = " << info->sourceExec << endl; +// myDebug() << "Manager::installScript() - sourceName = " << info->sourceName << endl; +// myDebug() << "Manager::installScript() - specFile = " << info->specFile << endl; + + delete ref; + return true; +} + +bool Manager::removeScript(const QString& name_) { + KConfigGroup fileGroup(KGlobal::config(), "KNewStuffFiles"); + QStringList files = fileGroup.readListEntry(name_); + + bool success = true; + QString path = Tellico::saveLocation(QString::fromLatin1("data-sources/")); + for(QStringList::ConstIterator it = files.begin(); it != files.end(); ++it) { + if((*it).endsWith(QChar('/'))) { + // ok to not delete all directories + QDir().rmdir(path + *it); + } else { + success = success && QFile(path + *it).remove(); + if(!success) { + myDebug() << "Manager::removeScript() - failed to remove " << (path+*it) << endl; + } + } + } + + // remove config entries even if unsuccessful + fileGroup.deleteEntry(name_); + KConfigGroup statusGroup(KGlobal::config(), "KNewStuffStatus"); + QString entryName = statusGroup.readEntry(name_); + statusGroup.deleteEntry(name_); + statusGroup.deleteEntry(entryName); + + return success; +} + +Tellico::NewStuff::InstallStatus Manager::installStatus(KNS::Entry* entry_) { + KConfigGroup config(KGlobal::config(), "KNewStuffStatus"); + QString datestring = config.readEntry(entry_->name()); + if(datestring.isEmpty()) { + return NotInstalled; + } + + QDate date = QDate::fromString(datestring, Qt::ISODate); + if(!date.isValid()) { + return NotInstalled; + } + if(date < entry_->releaseDate()) { + return OldVersion; + } + + // also check that executable files exists + KConfigGroup fileGroup(KGlobal::config(), "KNewStuffFiles"); + QStringList files = fileGroup.readListEntry(entry_->name()); + QString path = Tellico::saveLocation(QString::fromLatin1("data-sources/")); + for(QStringList::ConstIterator it = files.begin(); it != files.end(); ++it) { + if(!QFile::exists(path + *it)) { + return NotInstalled; + } + } + return Current; +} + +QStringList Manager::archiveFiles(const KArchiveDirectory* dir_, const QString& path_) { + QStringList list; + + const QStringList dirEntries = dir_->entries(); + for(QStringList::ConstIterator it = dirEntries.begin(); it != dirEntries.end(); ++it) { + const QString& entry = *it; + const KArchiveEntry* curEntry = dir_->entry(entry); + if(curEntry->isFile()) { + list += path_ + entry; + } else if(curEntry->isDirectory()) { + list += archiveFiles(static_cast<const KArchiveDirectory*>(curEntry), path_ + entry + '/'); + // add directory AFTER contents, since we delete from the top down later + list += path_ + entry + '/'; + } + } + + return list; +} + +// don't recurse, the .xsl must be in top directory +QString Manager::findXSL(const KArchiveDirectory* dir_) { + const QStringList entries = dir_->entries(); + for(QStringList::ConstIterator it = entries.begin(); it != entries.end(); ++it) { + const QString& entry = *it; + if(entry.endsWith(QString::fromLatin1(".xsl"))) { + return entry; + } + } + return QString(); +} + +QString Manager::findEXE(const KArchiveDirectory* dir_) { + QPtrStack<KArchiveDirectory> dirStack; + QValueStack<QString> dirNameStack; + + dirStack.push(dir_); + dirNameStack.push(QString()); + + do { + const QString dirName = dirNameStack.pop(); + const KArchiveDirectory* curDir = dirStack.pop(); + const QStringList entries = curDir->entries(); + for(QStringList::ConstIterator it = entries.begin(); it != entries.end(); ++it) { + const QString& entry = *it; + const KArchiveEntry* archEntry = curDir->entry(entry); + + if(archEntry->isFile() && (archEntry->permissions() & S_IEXEC)) { + return dirName + entry; + } else if(archEntry->isDirectory()) { + dirStack.push(static_cast<const KArchiveDirectory*>(archEntry)); + dirNameStack.push(dirName + entry + '/'); + } + } + } while(!dirStack.isEmpty()); + + return QString(); +} + +void Manager::install(DataType type_, KNS::Entry* entry_) { + if(m_tempFile) { + delete m_tempFile; + } + m_tempFile = new KTempFile(); + m_tempFile->setAutoDelete(true); + + KURL destination; + destination.setPath(m_tempFile->name()); + KIO::FileCopyJob* job = KIO::file_copy(entry_->payload(), destination, -1, true); + connect(job, SIGNAL(result(KIO::Job*)), SLOT(slotDownloadJobResult(KIO::Job*))); + m_jobMap.insert(job, EntryPair(entry_, type_)); +} + +void Manager::slotDownloadJobResult(KIO::Job* job_) { + KIO::FileCopyJob* job = static_cast<KIO::FileCopyJob*>(job_); + if(job->error()) { + GUI::CursorSaver cs(Qt::arrowCursor); + delete m_tempFile; + m_tempFile = 0; + job->showErrorDialog(Kernel::self()->widget()); + emit signalInstalled(0); // still need to notify dialog that install failed + return; + } + + KNS::Entry* entry = m_jobMap[job_].first; + DataType type = m_jobMap[job_].second; + + bool deleteTempFile = true; + if(type == EntryTemplate) { + installTemplate(job->destURL(), entry->name()); + } else { +#if KDE_IS_VERSION(3,3,90) + // needed so the GPG signature can be checked + NewScript* newScript = new NewScript(this, Kernel::self()->widget()); + connect(newScript, SIGNAL(installFinished()), this, SLOT(slotInstallFinished())); + // need to delete temp file if install was not a success + // if it was a success, it gets deleted later + deleteTempFile = !newScript->install(job->destURL().path()); + m_scriptEntryMap.insert(newScript, entry); +#endif + // if failed, emit empty signal now + if(deleteTempFile) { + emit signalInstalled(0); + } + } + if(deleteTempFile) { + delete m_tempFile; + m_tempFile = 0; + } +} + +void Manager::slotInstallFinished() { + const NewScript* newScript = ::qt_cast<const NewScript*>(sender()); + if(newScript && newScript->successfulInstall()) { + const QString name = m_urlNameMap[newScript->url()]; + KNS::Entry* entry = m_scriptEntryMap[newScript]; + KConfigGroup config(KGlobal::config(), "KNewStuffStatus"); + // have to keep a config entry that maps the name of the file to the name + // of the newstuff entry, so we can track which entries are installed + // name and entry-name() are probably the same for scripts, but not a problem + config.writeEntry(name, entry->name()); + config.writeEntry(entry->name(), entry->releaseDate().toString(Qt::ISODate)); + config.sync(); + emit signalInstalled(entry); + } else { + emit signalInstalled(0); + kdWarning() << "Manager::slotInstallFinished() - Failed to install" << endl; + } + delete m_tempFile; + m_tempFile = 0; +} + +bool Manager::checkCommonFile() { + // look for a file that gets installed to know the installation directory + // need to check timestamps + QString userDataDir = Tellico::saveLocation(QString::null); + QString userCommonFile = userDataDir + '/' + QString::fromLatin1("tellico-common.xsl"); + if(QFile::exists(userCommonFile)) { + // check timestamps + // pics/tellico.png is not likely to be in a user directory + QString installDir = KGlobal::dirs()->findResourceDir("appdata", QString::fromLatin1("pics/tellico.png")); + QString installCommonFile = installDir + '/' + QString::fromLatin1("tellico-common.xsl"); +#ifndef NDEBUG + if(userCommonFile == installCommonFile) { + kdWarning() << "Manager::checkCommonFile() - install location is same as user location" << endl; + } +#endif + QFileInfo installInfo(installCommonFile); + QFileInfo userInfo(userCommonFile); + if(installInfo.lastModified() > userInfo.lastModified()) { + // the installed file has been modified more recently than the user's + // remove user's tellico-common.xsl file so it gets copied again + myLog() << "Manager::checkCommonFile() - removing " << userCommonFile << endl; + myLog() << "Manager::checkCommonFile() - copying " << installCommonFile << endl; + QFile::remove(userCommonFile); + } else { + return true; + } + } + KURL src, dest; + src.setPath(KGlobal::dirs()->findResource("appdata", QString::fromLatin1("tellico-common.xsl"))); + dest.setPath(userCommonFile); + return KIO::NetAccess::file_copy(src, dest); +} + +#include "manager.moc" |