• Skip to content
  • Skip to link menu
KDE 4.6 API Reference
  • KDE API Reference
  • kdelibs
  • KDE Home
  • Contact Us
 

KNewStuff

installation.cpp

Go to the documentation of this file.
00001 /*
00002     This file is part of KNewStuff2.
00003     Copyright (c) 2007 Josef Spillner <spillner@kde.org>
00004     Copyright (C) 2009 Frederik Gladhorn <gladhorn@kde.org>
00005 
00006     This library is free software; you can redistribute it and/or
00007     modify it under the terms of the GNU Lesser General Public
00008     License as published by the Free Software Foundation; either
00009     version 2.1 of the License, or (at your option) any later version.
00010 
00011     This library is distributed in the hope that it will be useful,
00012     but WITHOUT ANY WARRANTY; without even the implied warranty of
00013     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00014     Lesser General Public License for more details.
00015 
00016     You should have received a copy of the GNU Lesser General Public
00017     License along with this library.  If not, see <http://www.gnu.org/licenses/>.
00018 */
00019 
00020 #include "installation.h"
00021 
00022 #include <QDir>
00023 #include <QFile>
00024 
00025 #include "kstandarddirs.h"
00026 #include "kmimetype.h"
00027 #include "karchive.h"
00028 #include "kzip.h"
00029 #include "ktar.h"
00030 #include "kprocess.h"
00031 #include "kio/job.h"
00032 #include "krandom.h"
00033 #include "kshell.h"
00034 #include "kmessagebox.h" // TODO get rid of message box
00035 #include "ktoolinvocation.h" // TODO remove, this was only for my playing round
00036 #include "klocalizedstring.h"
00037 #include "kdebug.h"
00038 
00039 #include "core/security.h"
00040 #ifdef Q_OS_WIN
00041 #include <windows.h>
00042 #include <shlobj.h>
00043 #endif
00044 
00045 using namespace KNS3;
00046 
00047 Installation::Installation(QObject* parent)
00048     : QObject(parent)
00049     , checksumPolicy(Installation::CheckIfPossible)
00050     , signaturePolicy(Installation::CheckIfPossible)
00051     , scope(Installation::ScopeUser)
00052     , customName(false)
00053     , acceptHtml(false)
00054 {
00055 }
00056 
00057 bool Installation::readConfig(const KConfigGroup& group)
00058 {
00059     // FIXME: add support for several categories later on
00060     // FIXME: read out only when actually installing as a performance improvement?
00061     QString uncompresssetting = group.readEntry("Uncompress", QString("never"));
00062     // support old value of true as equivalent of always
00063     if (uncompresssetting == "true") {
00064         uncompresssetting = "always";
00065     }
00066     if (uncompresssetting != "always" && uncompresssetting != "archive" && uncompresssetting != "never") {
00067         kError() << "invalid Uncompress setting chosen, must be one of: always, archive, or never" << endl;
00068         return false;
00069     }
00070     uncompression = uncompresssetting;
00071     postInstallationCommand = group.readEntry("InstallationCommand", QString());
00072     uninstallCommand = group.readEntry("UninstallCommand", QString());
00073     standardResourceDirectory = group.readEntry("StandardResource", QString());
00074     targetDirectory = group.readEntry("TargetDir", QString());
00075     xdgTargetDirectory = group.readEntry("XdgTargetDir", QString());
00076     installPath = group.readEntry("InstallPath", QString());
00077     absoluteInstallPath = group.readEntry("AbsoluteInstallPath", QString());
00078     customName = group.readEntry("CustomName", false);
00079     acceptHtml = group.readEntry("AcceptHtmlDownloads", false);
00080 
00081     if (standardResourceDirectory.isEmpty() &&
00082             targetDirectory.isEmpty() &&
00083             xdgTargetDirectory.isEmpty() &&
00084             installPath.isEmpty() &&
00085             absoluteInstallPath.isEmpty()) {
00086         kError() << "No installation target set";
00087         return false;
00088     }
00089 
00090     QString checksumpolicy = group.readEntry("ChecksumPolicy", QString());
00091     if (!checksumpolicy.isEmpty()) {
00092         if (checksumpolicy == "never")
00093             checksumPolicy = Installation::CheckNever;
00094         else if (checksumpolicy == "ifpossible")
00095             checksumPolicy = Installation::CheckIfPossible;
00096         else if (checksumpolicy == "always")
00097             checksumPolicy = Installation::CheckAlways;
00098         else {
00099             kError() << "The checksum policy '" + checksumpolicy + "' is unknown." << endl;
00100             return false;
00101         }
00102     }
00103 
00104     QString signaturepolicy = group.readEntry("SignaturePolicy", QString());
00105     if (!signaturepolicy.isEmpty()) {
00106         if (signaturepolicy == "never")
00107             signaturePolicy = Installation::CheckNever;
00108         else if (signaturepolicy == "ifpossible")
00109             signaturePolicy = Installation::CheckIfPossible;
00110         else if (signaturepolicy == "always")
00111             signaturePolicy = Installation::CheckAlways;
00112         else {
00113             kError() << "The signature policy '" + signaturepolicy + "' is unknown." << endl;
00114             return false;
00115         }
00116     }
00117 
00118     QString scopeString = group.readEntry("Scope", QString());
00119     if (!scopeString.isEmpty()) {
00120         if (scopeString == "user")
00121             scope = ScopeUser;
00122         else if (scopeString == "system")
00123             scope = ScopeSystem;
00124         else {
00125             kError() << "The scope '" + scopeString + "' is unknown." << endl;
00126             return false;
00127         }
00128 
00129         if (scope == ScopeSystem) {
00130             if (!installPath.isEmpty()) {
00131                 kError() << "System installation cannot be mixed with InstallPath." << endl;
00132                 return false;
00133             }
00134         }
00135     }
00136     return true;
00137 }
00138 
00139 bool Installation::isRemote() const
00140 {
00141     if (!installPath.isEmpty()) return false;
00142     if (!targetDirectory.isEmpty()) return false;
00143     if (!xdgTargetDirectory.isEmpty()) return false;
00144     if (!absoluteInstallPath.isEmpty()) return false;
00145     if (!standardResourceDirectory.isEmpty()) return false;
00146     return true;
00147 }
00148 
00149 void Installation::install(EntryInternal entry)
00150 {
00151     downloadPayload(entry);
00152 }
00153 
00154 void Installation::downloadPayload(const KNS3::EntryInternal& entry)
00155 {
00156     if(!entry.isValid()) {
00157         emit signalInstallationFailed(i18n("Invalid item."));
00158         return;
00159     }
00160     KUrl source = KUrl(entry.payload());
00161 
00162     if (!source.isValid()) {
00163         kError() << "The entry doesn't have a payload." << endl;
00164         emit signalInstallationFailed(i18n("Download of item failed: no download URL for \"%1\".", entry.name()));
00165         return;
00166     }
00167 
00168     // FIXME no clue what this is supposed to do
00169     if (isRemote()) {
00170         // Remote resource
00171         //kDebug() << "Relaying remote payload '" << source << "'";
00172         install(entry, source.pathOrUrl());
00173         emit signalPayloadLoaded(source);
00174         // FIXME: we still need registration for eventual deletion
00175         return;
00176     }
00177 
00178     QString fileName(source.fileName());
00179     KUrl destination = QString(KGlobal::dirs()->saveLocation("tmp") + KRandom::randomString(10) + '-' + fileName);
00180     kDebug() << "Downloading payload '" << source << "' to '" << destination << "'";
00181 
00182     // FIXME: check for validity
00183     KIO::FileCopyJob *job = KIO::file_copy(source, destination, -1, KIO::Overwrite | KIO::HideProgressInfo);
00184     connect(job,
00185             SIGNAL(result(KJob*)),
00186             SLOT(slotPayloadResult(KJob*)));
00187 
00188     entry_jobs[job] = entry;
00189 }
00190 
00191 
00192 void Installation::slotPayloadResult(KJob *job)
00193 {
00194     // for some reason this slot is getting called 3 times on one job error
00195     if (entry_jobs.contains(job)) {
00196         EntryInternal entry = entry_jobs[job];
00197         entry_jobs.remove(job);
00198 
00199         if (job->error()) {
00200             emit signalInstallationFailed(i18n("Download of \"%1\" failed, error: %2", entry.name(), job->errorString()));
00201         } else {
00202             KIO::FileCopyJob *fcjob = static_cast<KIO::FileCopyJob*>(job);
00203 
00204             // check if the app likes html files - disabled by default as too many bad links have been submitted to opendesktop.org
00205             if (!acceptHtml) {
00206                 KMimeType::Ptr mimeType = KMimeType::findByPath(fcjob->destUrl().toLocalFile());
00207                 if (mimeType->is("text/html") || mimeType->is("application/x-php")) {
00208                     if (KMessageBox::questionYesNo(0, i18n("The downloaded file is a html file. This indicates a link to a website instead of the actual download. Would you like to open the site with a browser instead?"), i18n("Possibly bad download link"))
00209                         == KMessageBox::Yes) {
00210                         KToolInvocation::invokeBrowser(fcjob->srcUrl().url());
00211                         emit signalInstallationFailed(i18n("Downloaded file was a HTML file. Opened in browser."));
00212                         entry.setStatus(Entry::Invalid);
00213                         emit signalEntryChanged(entry);
00214                         return;
00215                     }
00216                 }
00217             }
00218 
00219             install(entry, fcjob->destUrl().toLocalFile());
00220             emit signalPayloadLoaded(fcjob->destUrl());
00221         }
00222     }
00223 }
00224 
00225 
00226 void Installation::install(KNS3::EntryInternal entry, const QString& downloadedFile)
00227 {
00228     kDebug() << "Install: " << entry.name() << " from " << downloadedFile;
00229 
00230     if (entry.payload().isEmpty()) {
00231         kDebug() << "No payload associated with: " << entry.name();
00232         return;
00233     }
00234 
00235     // FIXME: first of all, do the security stuff here
00236     // this means check sum comparison and signature verification
00237     // signature verification might take a long time - make async?!
00238     /*
00239     if (checksumPolicy() != Installation::CheckNever) {
00240         if (entry.checksum().isEmpty()) {
00241             if (checksumPolicy() == Installation::CheckIfPossible) {
00242                 //kDebug() << "Skip checksum verification";
00243             } else {
00244                 kError() << "Checksum verification not possible" << endl;
00245                 return false;
00246             }
00247         } else {
00248             //kDebug() << "Verify checksum...";
00249         }
00250     }
00251     if (signaturePolicy() != Installation::CheckNever) {
00252         if (entry.signature().isEmpty()) {
00253             if (signaturePolicy() == Installation::CheckIfPossible) {
00254                 //kDebug() << "Skip signature verification";
00255             } else {
00256                 kError() << "Signature verification not possible" << endl;
00257                 return false;
00258             }
00259         } else {
00260             //kDebug() << "Verify signature...";
00261         }
00262     }
00263     */
00264 
00265     QString targetPath = targetInstallationPath(downloadedFile);
00266     QStringList installedFiles = installDownloadedFileAndUncompress(entry, downloadedFile, targetPath);
00267 
00268     if (installedFiles.isEmpty()) {
00269         if (entry.status() == Entry::Installing) {
00270             entry.setStatus(Entry::Downloadable);
00271         } else if (entry.status() == Entry::Updating) {
00272             entry.setStatus(Entry::Updateable);
00273         }
00274         emit signalEntryChanged(entry);
00275         emit signalInstallationFailed(i18n("Could not install \"%1\": file not found.", entry.name()));
00276         return;
00277     }
00278 
00279     entry.setInstalledFiles(installedFiles);
00280 
00281     if (!postInstallationCommand.isEmpty()) {
00282         QString target;
00283         if (installedFiles.size() == 1) {
00284             runPostInstallationCommand(installedFiles.first());
00285         } else {
00286             runPostInstallationCommand(targetPath);
00287         }
00288     }
00289 
00290     // ==== FIXME: security code below must go above, when async handling is complete ====
00291 
00292     // FIXME: security object lifecycle - it is a singleton!
00293     Security *sec = Security::ref();
00294 
00295     connect(sec,
00296             SIGNAL(validityResult(int)),
00297             SLOT(slotInstallationVerification(int)));
00298 
00299     // FIXME: change to accept filename + signature
00300     sec->checkValidity(QString());
00301 
00302     // update version and release date to the new ones
00303     if (entry.status() == Entry::Updating) {
00304         if (!entry.updateVersion().isEmpty()) {
00305             entry.setVersion(entry.updateVersion());
00306         }
00307         if (entry.updateReleaseDate().isValid()) {
00308             entry.setReleaseDate(entry.updateReleaseDate());
00309         }
00310     }
00311 
00312     entry.setStatus(Entry::Installed);
00313     emit signalEntryChanged(entry);
00314     emit signalInstallationFinished();
00315 }
00316 
00317 QString Installation::targetInstallationPath(const QString& payloadfile)
00318 {
00319     QString installpath(payloadfile);
00320     QString installdir;
00321 
00322     if (!isRemote()) {
00323         // installdir is the target directory
00324 
00325         // installpath also contains the file name if it's a single file, otherwise equal to installdir
00326         int pathcounter = 0;
00327         if (!standardResourceDirectory.isEmpty()) {
00328             if (scope == ScopeUser) {
00329                 installdir = KStandardDirs::locateLocal(standardResourceDirectory.toUtf8(), "/");
00330             } else { // system scope
00331                 installdir = KStandardDirs::installPath(standardResourceDirectory.toUtf8());
00332             }
00333             pathcounter++;
00334         }
00335         if (!targetDirectory.isEmpty()) {
00336             if (scope == ScopeUser) {
00337                 installdir = KStandardDirs::locateLocal("data", targetDirectory + '/');
00338             } else { // system scope
00339                 installdir = KStandardDirs::installPath("data") + targetDirectory + '/';
00340             }
00341             pathcounter++;
00342         }
00343         if (!xdgTargetDirectory.isEmpty()) {
00344             installdir = KStandardDirs().localxdgdatadir() + '/' + xdgTargetDirectory + '/';
00345             pathcounter++;
00346         }
00347         if (!installPath.isEmpty()) {
00348 #if defined(Q_WS_WIN)
00349 #ifndef _WIN32_WCE
00350             WCHAR wPath[MAX_PATH+1];
00351             if ( SHGetFolderPathW(NULL, CSIDL_APPDATA, NULL, SHGFP_TYPE_CURRENT, wPath) == S_OK) {
00352                 installdir = QString::fromUtf16((const ushort *) wPath) + QLatin1Char('/') + installpath + QLatin1Char('/');
00353             } else {
00354 #endif
00355                 installdir =  QDir::home().path() + QLatin1Char('/') + installPath + QLatin1Char('/');
00356 #ifndef _WIN32_WCE
00357             }
00358 #endif
00359 #else
00360             installdir = QDir::home().path() + '/' + installPath + '/';
00361 #endif
00362             pathcounter++;
00363         }
00364         if (!absoluteInstallPath.isEmpty()) {
00365             installdir = absoluteInstallPath + '/';
00366             pathcounter++;
00367         }
00368         if (pathcounter != 1) {
00369             kError() << "Wrong number of installation directories given." << endl;
00370             return QString();
00371         }
00372 
00373         kDebug() << "installdir: " << installdir;
00374 
00375     }
00376 
00377     return installdir;
00378 }
00379 
00380 QStringList Installation::installDownloadedFileAndUncompress(const KNS3::EntryInternal&  entry, const QString& payloadfile, const QString installdir)
00381 {
00382     QString installpath(payloadfile);
00383     // Collect all files that were installed
00384     QStringList installedFiles;
00385 
00386     if (!isRemote()) {
00387         bool isarchive = true;
00388 
00389         // respect the uncompress flag in the knsrc
00390         if (uncompression == "always" || uncompression == "archive") {
00391             // this is weird but a decompression is not a single name, so take the path instead
00392             installpath = installdir;
00393             KMimeType::Ptr mimeType = KMimeType::findByPath(payloadfile);
00394             //kDebug() << "Postinstallation: uncompress the file";
00395 
00396             // FIXME: check for overwriting, malicious archive entries (../foo) etc.
00397             // FIXME: KArchive should provide "safe mode" for this!
00398             KArchive *archive = 0;
00399     
00400 
00401             if (mimeType->is("application/zip")) {
00402                 archive = new KZip(payloadfile);
00403             } else if (mimeType->is("application/tar")
00404                        || mimeType->is("application/x-gzip")
00405                        || mimeType->is("application/x-bzip")
00406                        || mimeType->is("application/x-lzma")
00407                        || mimeType->is("application/x-xz")
00408                        || mimeType->is("application/x-bzip-compressed-tar")
00409                        || mimeType->is("application/x-compressed-tar") ) {
00410                 archive = new KTar(payloadfile);
00411             } else {
00412                 delete archive;
00413                 kError() << "Could not determine type of archive file '" << payloadfile << "'";
00414                 if (uncompression == "always") {
00415                     return QStringList();
00416                 }
00417                 isarchive = false;
00418             }
00419 
00420             if (isarchive) {
00421                 bool success = archive->open(QIODevice::ReadOnly);
00422                 if (!success) {
00423                     kError() << "Cannot open archive file '" << payloadfile << "'";
00424                     if (uncompression == "always") {
00425                         return QStringList();
00426                     }
00427                     // otherwise, just copy the file
00428                     isarchive = false;
00429                 }
00430 
00431                 if (isarchive) {
00432                     const KArchiveDirectory *dir = archive->directory();
00433                     dir->copyTo(installdir);
00434 
00435                     installedFiles << archiveEntries(installdir, dir);
00436                     installedFiles << installdir + '/';
00437 
00438                     archive->close();
00439                     QFile::remove(payloadfile);
00440                     delete archive;
00441                 }
00442             }
00443         }
00444 
00445         kDebug() << "isarchive: " << isarchive;
00446 
00447         if (uncompression == "never" || (uncompression == "archive" && !isarchive)) {
00448             // no decompress but move to target
00449 
00451             // FIXME: make naming convention configurable through *.knsrc? e.g. for kde-look.org image names
00452             KUrl source = KUrl(entry.payload());
00453             kDebug() << "installing non-archive from " << source.url();
00454             QString installfile;
00455             QString ext = source.fileName().section('.', -1);
00456             if (customName) {
00457                 installfile = entry.name();
00458                 installfile += '-' + entry.version();
00459                 if (!ext.isEmpty()) installfile += '.' + ext;
00460             } else {
00461                 installfile = source.fileName();
00462             }
00463             installpath = installdir + '/' + installfile;
00464 
00465             //kDebug() << "Install to file " << installpath;
00466             // FIXME: copy goes here (including overwrite checking)
00467             // FIXME: what must be done now is to update the cache *again*
00468             //        in order to set the new payload filename (on root tag only)
00469             //        - this might or might not need to take uncompression into account
00470             // FIXME: for updates, we might need to force an overwrite (that is, deleting before)
00471             QFile file(payloadfile);
00472             bool success = true;
00473             const bool update = ((entry.status() == Entry::Updateable) || (entry.status() == Entry::Updating));
00474 
00475             if (QFile::exists(installpath)) {
00476                 if (!update) {
00477                     if (KMessageBox::warningContinueCancel(0, i18n("Overwrite existing file?") + "\n'" + installpath + '\'', i18n("Download File:")) == KMessageBox::Cancel) {
00478                         return QStringList();
00479                     }
00480                 }
00481                 success = QFile::remove(installpath);
00482             }
00483             if (success) {
00484                 success = file.rename(KUrl(installpath).toLocalFile());
00485                 kDebug() << "move: " << file.fileName() << " to " << installpath;
00486             }
00487             if (!success) {
00488                 kError() << "Cannot move file '" << payloadfile << "' to destination '"  << installpath << "'";
00489                 return QStringList();
00490             }
00491             installedFiles << installpath;
00492         }
00493     }
00494     return installedFiles;
00495 }
00496 
00497 void Installation::runPostInstallationCommand(const QString& installPath)
00498 {
00499     KProcess process;
00500     QString command(postInstallationCommand);
00501     QString fileArg(KShell::quoteArg(installPath));
00502     command.replace("%f", fileArg);
00503 
00504     kDebug() << "Run command: " << command;
00505 
00506     process.setShellCommand(command);
00507     int exitcode = process.execute();
00508 
00509     if (exitcode) {
00510         kError() << "Command failed" << endl;
00511     }
00512 }
00513 
00514 
00515 void Installation::uninstall(EntryInternal entry)
00516 {
00517     entry.setStatus(Entry::Deleted);
00518 
00519     if (!uninstallCommand.isEmpty()) {
00520         KProcess process;
00521         foreach (const QString& file, entry.installedFiles()) {
00522             QFileInfo info(file);
00523             if (info.isFile()) {
00524                 QString fileArg(KShell::quoteArg(file));
00525                 QString command(uninstallCommand);
00526                 command.replace("%f", fileArg);
00527 
00528                 process.setShellCommand(command);
00529                 int exitcode = process.execute();
00530 
00531                 if (exitcode) {
00532                     kError() << "Command failed" << endl;
00533                 } else {
00534                     //kDebug() << "Command executed successfully";
00535                 }
00536             }
00537         }
00538     }
00539 
00540     foreach(const QString &file, entry.installedFiles()) {
00541         if (file.endsWith('/')) {
00542             QDir dir;
00543             bool worked = dir.rmdir(file);
00544             if (!worked) {
00545                 // Maybe directory contains user created files, ignore it
00546                 continue;
00547             }
00548         } else {
00549             if (QFile::exists(file)) {
00550                 bool worked = QFile::remove(file);
00551                 if (!worked) {
00552                     kWarning() << "unable to delete file " << file;
00553                     return;
00554                 }
00555             } else {
00556                 kWarning() << "unable to delete file " << file << ". file does not exist.";
00557             }
00558         }
00559     }
00560     entry.setUnInstalledFiles(entry.installedFiles());
00561     entry.setInstalledFiles(QStringList());
00562 
00563     emit signalEntryChanged(entry);
00564 }
00565 
00566 
00567 void Installation::slotInstallationVerification(int result)
00568 {
00569     //kDebug() << "SECURITY result " << result;
00570 
00571     //FIXME do something here ??? and get the right entry again
00572     EntryInternal entry;
00573 
00574     if (result & Security::SIGNED_OK)
00575         emit signalEntryChanged(entry);
00576     else
00577         emit signalEntryChanged(entry);
00578 }
00579 
00580 
00581 QStringList Installation::archiveEntries(const QString& path, const KArchiveDirectory * dir)
00582 {
00583     QStringList files;
00584     foreach(const QString &entry, dir->entries()) {
00585         QString childPath = path + '/' + entry;
00586         if (dir->entry(entry)->isFile()) {
00587             files << childPath;
00588         }
00589 
00590         if (dir->entry(entry)->isDirectory()) {
00591             const KArchiveDirectory* childDir = static_cast<const KArchiveDirectory*>(dir->entry(entry));
00592             files << archiveEntries(childPath, childDir);
00593             files << childPath + '/';
00594         }
00595     }
00596     return files;
00597 }
00598 
00599 
00600 #include "installation.moc"

KNewStuff

Skip menu "KNewStuff"
  • Main Page
  • Namespace List
  • Class Hierarchy
  • Alphabetical List
  • Class List
  • File List
  • Namespace Members
  • Class Members
  • Related Pages

kdelibs

Skip menu "kdelibs"
  • DNSSD
  • Interfaces
  •   KHexEdit
  •   KMediaPlayer
  •   KSpeech
  •   KTextEditor
  • Kate
  • kconf_update
  • KDE3Support
  •   KUnitTest
  • KDECore
  • KDED
  • KDEsu
  • KDEUI
  • KDEWebKit
  • KDocTools
  • KFile
  • KHTML
  • KImgIO
  • KInit
  • kio
  • KIOSlave
  • KJS
  •   KJS-API
  •   WTF
  • kjsembed
  • KNewStuff
  • KParts
  • KPty
  • Kross
  • KUnitConversion
  • KUtils
  • Nepomuk
  • Plasma
  • Solid
  • Sonnet
  • ThreadWeaver
Generated for kdelibs by doxygen 1.7.3
This website is maintained by Adriaan de Groot and Allen Winter.
KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal