QString InstalledPackages::setPackageVersionPath(const QString& package,
        const Version& version,
        const QString& directory, bool updateRegistry)
{
    this->mutex.lock();

    QString err;

    InstalledPackageVersion* ipv = this->findNoCopy(package, version);
    if (!ipv) {
        ipv = new InstalledPackageVersion(package, version, directory);
        this->data.insert(package + "/" + version.getVersionString(), ipv);
        if (updateRegistry)
            err = saveToRegistry(ipv);
    } else {
        ipv->setPath(directory);
        if (updateRegistry)
            err = saveToRegistry(ipv);
    }

    this->mutex.unlock();

    fireStatusChanged(package, version);

    return err;
}
bool InstalledPackages::isInstalled(const QString &package,
        const Version &version) const
{
    this->mutex.lock();

    InstalledPackageVersion* ipv = findNoCopy(package, version);
    bool r = ipv && ipv->installed();

    this->mutex.unlock();

    return r;
}
QString InstalledPackages::getPath(const QString &package,
        const Version &version) const
{
    this->mutex.lock();

    QString r;
    InstalledPackageVersion* ipv = findNoCopy(package, version);
    if (ipv)
        r = ipv->getDirectory();

    this->mutex.unlock();

    return r;
}
InstalledPackageVersion* InstalledPackages::find(const QString& package,
        const Version& version) const
{
    this->mutex.lock();

    InstalledPackageVersion* ipv = this->data.value(
            PackageVersion::getStringId(package, version));
    if (ipv)
        ipv = ipv->clone();

    this->mutex.unlock();

    return ipv;
}
QSet<QString> InstalledPackages::getPackages() const
{
    this->mutex.lock();

    QList<InstalledPackageVersion*> all = this->data.values();
    QSet<QString> r;
    for (int i = 0; i < all.count(); i++) {
        InstalledPackageVersion* ipv = all.at(i);
        if (ipv->installed())
            r.insert(ipv->package);
    }

    this->mutex.unlock();

    return r;
}
QList<InstalledPackageVersion*> InstalledPackages::getAll() const
{
    this->mutex.lock();

    QList<InstalledPackageVersion*> all = this->data.values();
    QList<InstalledPackageVersion*> r;
    for (int i = 0; i < all.count(); i++) {
        InstalledPackageVersion* ipv = all.at(i);
        if (ipv->installed())
            r.append(ipv->clone());
    }

    this->mutex.unlock();

    return r;
}
InstalledPackageVersion *AbstractRepository::findHighestInstalledMatch(
        const Dependency &dep) const
{
    QList<InstalledPackageVersion*> list = findAllInstalledMatches(dep);
    InstalledPackageVersion* res = 0;
    for (int i = 0; i < list.count(); i++) {
        InstalledPackageVersion* ipv = list.at(i);
        if (res == 0 || ipv->version.compare(res->version) > 0)
            res = ipv;
    }
    if (res)
        res = res->clone();
    qDeleteAll(list);

    return res;
}
QStringList InstalledPackages::getAllInstalledPackagePaths() const
{
    this->mutex.lock();

    QStringList r;
    QList<InstalledPackageVersion*> ipvs = this->data.values();
    for (int i = 0; i < ipvs.count(); i++) {
        InstalledPackageVersion* ipv = ipvs.at(i);
        if (ipv->installed())
            r.append(ipv->getDirectory());
    }

    this->mutex.unlock();

    return r;
}
QList<InstalledPackageVersion *> AbstractRepository::findAllInstalledMatches(
        const Dependency &dep) const
{
    QList<InstalledPackageVersion*> r;
    InstalledPackages* ip = InstalledPackages::getDefault();
    QList<InstalledPackageVersion*> installed = ip->getAll();
    for (int i = 0; i < installed.count(); i++) {
        InstalledPackageVersion* ipv = installed.at(i);
        if (ipv->package == dep.package &&
                dep.test(ipv->version)) {
            r.append(ipv->clone());
        }
    }
    qDeleteAll(installed);
    return r;
}
QString InstalledPackages::clearPackagesInNestedDirectories() {
    QString err;

    QList<InstalledPackageVersion*> pvs = this->getAll();
    qSort(pvs.begin(), pvs.end(), installedPackageVersionLessThan);

    // performance improvement: normalize the paths first
    QStringList paths;
    for (int i = 0; i < pvs.count(); i++) {
        InstalledPackageVersion* pv = pvs.at(i);
        QString p = WPMUtils::normalizePath(pv->getDirectory()) + '\\';
        paths.append(p);
    }

    for (int j = 0; j < pvs.count(); j++) {
        InstalledPackageVersion* pv = pvs.at(j);
        QString pvdir = paths.at(j);
        if (pv->installed()) {
            for (int i = j + 1; i < pvs.count(); i++) {
                InstalledPackageVersion* pv2 = pvs.at(i);
                QString pv2dir = paths.at(i);
                if (pv2->installed()) {
                    if (pv2dir.startsWith(pvdir)) {
                        err = setPackageVersionPath(pv2->package,
                                pv2->version, "");
                        if (!err.isEmpty())
                            goto out;
                    }
                }
            }
        }
    }
out:

    qDeleteAll(pvs);
    pvs.clear();

    return err;
}
Beispiel #11
0
InstalledPackageVersion*
        InstalledPackages::findFirstWithMissingDependency() const
{
    InstalledPackageVersion* r = 0;

    this->mutex.lock();

    DBRepository* dbr = DBRepository::getDefault();
    QList<InstalledPackageVersion*> all = this->data.values();
    for (int i = 0; i < all.count(); i++) {
        InstalledPackageVersion* ipv = all.at(i);
        if (ipv->installed()) {
            QString err;
            QScopedPointer<PackageVersion> pv(dbr->findPackageVersion_(
                    ipv->package, ipv->version, &err));

            // TODO: remove
            if (!pv.data()) {
                qDebug() << "cannot find" << ipv->package << ipv->version.getVersionString();
            }

            if (err.isEmpty() && pv.data()) {
                for (int j = 0; j < pv->dependencies.size(); j++) {
                    if (!isInstalled(*pv->dependencies.at(j))) {
                        r = ipv->clone();
                        break;
                    }
                }
            }
        }

        if (r)
            break;
    }

    this->mutex.unlock();

    return r;
}
InstalledPackageVersion* InstalledPackages::getNewestInstalled(
        const QString &package) const
{
    this->mutex.lock();

    QList<InstalledPackageVersion*> all = this->data.values();
    InstalledPackageVersion* r = 0;
    for (int i = 0; i < all.count(); i++) {
        InstalledPackageVersion* ipv = all.at(i);
        if (ipv->package == package && ipv->installed()) {
            if (!r || r->version < ipv->version)
                r = ipv;
        }
    }

    if (r)
        r = r->clone();

    this->mutex.unlock();

    return r;
}
QString AbstractRepository::computeNpackdCLEnvVar_(QString* err) const
{
    QString v;
    InstalledPackageVersion* ipv;
    if (WPMUtils::is64BitWindows()) {
        ipv = InstalledPackages::getDefault()->getNewestInstalled(
                "com.googlecode.windows-package-manager.NpackdCL64");
    } else
        ipv = 0;

    if (!ipv)
        ipv = InstalledPackages::getDefault()->getNewestInstalled(
            "com.googlecode.windows-package-manager.NpackdCL");

    if (ipv)
        v = ipv->getDirectory();

    delete ipv;

    // qDebug() << "computed NPACKD_CL" << v;

    return v;
}
InstalledPackageVersion *InstalledPackages::findOwner(
        const QString &filePath) const
{
    this->mutex.lock();

    InstalledPackageVersion* f = 0;
    QList<InstalledPackageVersion*> ipvs = this->data.values();
    for (int i = 0; i < ipvs.count(); ++i) {
        InstalledPackageVersion* ipv = ipvs.at(i);
        QString dir = ipv->getDirectory();
        if (!dir.isEmpty() && (WPMUtils::pathEquals(filePath, dir) ||
                WPMUtils::isUnder(filePath, dir))) {
            f = ipv;
            break;
        }
    }

    if (f)
        f = f->clone();

    this->mutex.unlock();

    return f;
}
QString InstalledPackages::readRegistryDatabase()
{
    // qDebug() << "start reading registry database";

    // "data" is only used at the bottom of this method

    QString err;

    WindowsRegistry packagesWR;
    LONG e;
    err = packagesWR.open(HKEY_LOCAL_MACHINE,
            "SOFTWARE\\Npackd\\Npackd\\Packages", false, KEY_READ, &e);

    QList<InstalledPackageVersion*> ipvs;
    if (e == ERROR_FILE_NOT_FOUND || e == ERROR_PATH_NOT_FOUND) {
        err = "";
    } else if (err.isEmpty()) {
        QStringList entries = packagesWR.list(&err);
        for (int i = 0; i < entries.count(); ++i) {
            QString name = entries.at(i);
            int pos = name.lastIndexOf("-");
            if (pos <= 0)
                continue;

            QString packageName = name.left(pos);
            if (!Package::isValidName(packageName))
                continue;

            QString versionName = name.right(name.length() - pos - 1);
            Version version;
            if (!version.setVersion(versionName))
                continue;

            WindowsRegistry entryWR;
            err = entryWR.open(packagesWR, name, KEY_READ);
            if (!err.isEmpty())
                continue;

            QString p = entryWR.get("Path", &err).trimmed();
            if (!err.isEmpty())
                continue;

            QString dir;
            if (p.isEmpty())
                dir = "";
            else {
                QDir d(p);
                if (d.exists()) {
                    dir = p;
                } else {
                    dir = "";
                }
            }

            if (dir.isEmpty()) {
                packagesWR.remove(name);
            } else {
                dir = WPMUtils::normalizePath(dir, false);

                InstalledPackageVersion* ipv = new InstalledPackageVersion(
                        packageName, version, dir);
                ipv->detectionInfo = entryWR.get("DetectionInfo", &err);
                if (!err.isEmpty()) {
                    // ignore
                    ipv->detectionInfo = "";
                    err = "";
                }

                if (!ipv->directory.isEmpty()) {
                    /*
                    qDebug() << "adding " << ipv->package <<
                            ipv->version.getVersionString() << "in" <<
                            ipv->directory;*/
                    ipvs.append(ipv);
                } else {
                    delete ipv;
                }
            }
        }
    }

    this->mutex.lock();
    qDeleteAll(this->data);
    this->data.clear();
    for (int i = 0; i < ipvs.count(); i++) {
        InstalledPackageVersion* ipv = ipvs.at(i);
        this->data.insert(PackageVersion::getStringId(ipv->package,
                ipv->version), ipv->clone());
    }
    this->mutex.unlock();

    for (int i = 0; i < ipvs.count(); i++) {
        InstalledPackageVersion* ipv = ipvs.at(i);
        fireStatusChanged(ipv->package, ipv->version);
    }
    qDeleteAll(ipvs);

    // qDebug() << "stop reading";

    return err;
}
Beispiel #16
0
void InstalledPackages::refresh(DBRepository *rep, Job *job, bool detectMSI)
{
    rep->currentRepository = 10000;

    // no direct usage of "data" here => no mutex

    if (job->shouldProceed()) {
        Job* sub = job->newSubJob(0.2,
                QObject::tr("Detecting directories deleted externally"));

        QList<InstalledPackageVersion*> ipvs = getAll();
        for (int i = 0; i < ipvs.count(); i++) {
            InstalledPackageVersion* ipv = ipvs.at(i);
            if (ipv->installed()) {
                QDir d(ipv->getDirectory());
                d.refresh();
                if (!d.exists()) {
                    QString err = this->setPackageVersionPath(
                            ipv->package, ipv->version, "");
                    if (!err.isEmpty())
                        job->setErrorMessage(err);
                }
            }
        }
        qDeleteAll(ipvs);

        sub->completeWithProgress();
    }

    if (job->shouldProceed()) {
        Job* sub = job->newSubJob(0.6,
                QObject::tr("Reading registry package database"));
        QString err = readRegistryDatabase();
        if (!err.isEmpty())
            job->setErrorMessage(err);
        sub->completeWithProgress();
    }

    if (job->shouldProceed()) {
        Job* sub = job->newSubJob(0.02,
                QObject::tr(
            "Correcting installation paths created by previous versions of Npackd"));
        QString windowsDir = WPMUtils::normalizePath(WPMUtils::getWindowsDir());
        QList<InstalledPackageVersion*> ipvs = this->getAll();
        for (int i = 0; i < ipvs.count(); i++) {
            InstalledPackageVersion* ipv = ipvs.at(i);
            if (ipv->installed()) {
                QString d = WPMUtils::normalizePath(ipv->directory);
                // qDebug() << ipv->package <<ipv->directory;
                if ((WPMUtils::isUnder(d, windowsDir) ||
                        WPMUtils::pathEquals(d, windowsDir)) &&
                        ipv->package != InstalledPackages::packageName) {
                    this->setPackageVersionPath(ipv->package, ipv->version, "");
                }
            }
        }
        qDeleteAll(ipvs);
        sub->completeWithProgress();
    }

    // adding well-known packages should happen before adding packages
    // determined from the list of installed packages to get better
    // package descriptions for com.microsoft.Windows64 and similar packages

    // detecting from the list of installed packages should happen first
    // as all other packages consult the list of installed packages. Secondly,
    // MSI or the programs from the control panel may be installed in strange
    // locations like "C:\" which "uninstalls" all packages installed by Npackd

    // MSI package detection should happen before the detection for
    // control panel programs
    if (job->shouldProceed()) {
        QList<AbstractThirdPartyPM*> tpms;
        tpms.append(new WellKnownProgramsThirdPartyPM(
                InstalledPackages::packageName));
        tpms.append(new InstalledPackagesThirdPartyPM());
        tpms.append(new MSIThirdPartyPM()); // true, msi:
        tpms.append(new ControlPanelThirdPartyPM()); // true, control-panel:

        QStringList jobTitles;
        jobTitles.append(QObject::tr("Adding well-known packages"));
        jobTitles.append(QObject::tr("Reading the list of packages installed by Npackd"));
        jobTitles.append(QObject::tr("Detecting MSI packages"));
        jobTitles.append(QObject::tr("Detecting software control panel packages"));

        QStringList prefixes;
        prefixes.append("");
        prefixes.append("");
        prefixes.append("msi:");
        prefixes.append("control-panel:");

        QList<Repository*> repositories;
        QList<QList<InstalledPackageVersion*>* > installeds;
        for (int i = 0; i < tpms.count(); i++) {
            repositories.append(new Repository());
            installeds.append(new QList<InstalledPackageVersion*>());
        }

        QList<QFuture<void> > futures;
        for (int i = 0; i < tpms.count(); i++) {
            AbstractThirdPartyPM* tpm = tpms.at(i);
            Job* s = job->newSubJob(0.1,
                    jobTitles.at(i), false, true);

            QFuture<void> future = QtConcurrent::run(
                    tpm,
                    &AbstractThirdPartyPM::scan, s,
                    installeds.at(i), repositories.at(i));
            futures.append(future);
        }

        for (int i = 0; i < futures.count(); i++) {
            futures[i].waitForFinished();

            job->setProgress(0.82 + (i + 1.0) / futures.count() * 0.05);
        }


        for (int i = 0; i < futures.count(); i++) {
            Job* sub = job->newSubJob(0.1,
                    QObject::tr("Detecting %1").arg(i), false, true);
            detect3rdParty(sub, rep, repositories.at(i),
                    *installeds.at(i),
                    i == 2 || i == 3,
                    prefixes.at(i));

            job->setProgress(0.87 + (i + 1.0) / futures.count() * 0.05);
        }

        qDeleteAll(repositories);
        qDeleteAll(installeds);
        qDeleteAll(tpms);
    }

    if (job->shouldProceed()) {
        Job* sub = job->newSubJob(0.02,
                QObject::tr("Setting the NPACKD_CL environment variable"));
        QString err = rep->updateNpackdCLEnvVar();
        if (!err.isEmpty())
            job->setErrorMessage(err);
        else
            sub->completeWithProgress();
    }

/*
 * use DISM API instead
    if (job->shouldProceed()) {
        Job* sub = job->newSubJob(0.01,
                QObject::tr("Detecting Component Based Servicing packages"),
                true, true);

        AbstractThirdPartyPM* pm = new CBSThirdPartyPM();
        detect3rdParty(sub, rep, pm, true, "cbs:");
        delete pm;
    }
 */

    // TODO: if (detectMSI) {

    if (job->shouldProceed()) {
        Job* sub = job->newSubJob(0.06,
                QObject::tr("Clearing information about installed package versions in nested directories"));
        QString err = clearPackagesInNestedDirectories();
        if (!err.isEmpty())
            job->setErrorMessage(err);
        else {
            sub->completeWithProgress();
            job->setProgress(1);
        }
    }

    job->complete();
}
void InstalledPackages::refresh(DBRepository *rep, Job *job, bool detectMSI)
{
    rep->currentRepository = 10000;

    // no direct usage of "data" here => no mutex

    /* Example:
0 :  0  ms
1 :  0  ms
2 :  143  ms
3 :  31  ms
4 :  5  ms
5 :  199  ms
6 :  3365  ms
7 :  378  ms
8 :  644  ms
     */
    HRTimer timer(9);
    timer.time(0);

    // qDebug() << "InstalledPackages::refresh.0";

    if (!job->isCancelled() && job->getErrorMessage().isEmpty()) {
        Job* sub = job->newSubJob(0.2,
                QObject::tr("Detecting directories deleted externally"));

        QList<InstalledPackageVersion*> ipvs = getAll();
        for (int i = 0; i < ipvs.count(); i++) {
            InstalledPackageVersion* ipv = ipvs.at(i);
            if (ipv->installed()) {
                QDir d(ipv->getDirectory());
                d.refresh();
                if (!d.exists()) {
                    QString err = this->setPackageVersionPath(
                            ipv->package, ipv->version, "");
                    if (!err.isEmpty())
                        job->setErrorMessage(err);
                }
            }
        }
        qDeleteAll(ipvs);

        sub->completeWithProgress();
    }

    timer.time(1);

    if (!job->isCancelled() && job->getErrorMessage().isEmpty()) {
        Job* sub = job->newSubJob(0.6,
                QObject::tr("Reading registry package database"));
        QString err = readRegistryDatabase();
        if (!err.isEmpty())
            job->setErrorMessage(err);
        sub->completeWithProgress();
    }

    timer.time(2);

    if (job->shouldProceed()) {
        Job* sub = job->newSubJob(0.02,
                QObject::tr(
            "Correcting installation paths created by previous versions of Npackd"));
        QString windowsDir = WPMUtils::normalizePath(WPMUtils::getWindowsDir());
        QList<InstalledPackageVersion*> ipvs = this->getAll();
        for (int i = 0; i < ipvs.count(); i++) {
            InstalledPackageVersion* ipv = ipvs.at(i);
            if (ipv->installed()) {
                QString d = WPMUtils::normalizePath(ipv->directory);
                // qDebug() << ipv->package <<ipv->directory;
                if ((WPMUtils::isUnder(d, windowsDir) ||
                        WPMUtils::pathEquals(d, windowsDir)) &&
                        ipv->package != packageName) {
                    this->setPackageVersionPath(ipv->package, ipv->version, "");
                }
            }
        }
        qDeleteAll(ipvs);
        sub->completeWithProgress();
    }

    // adding well-known packages should happen before adding packages
    // determined from the list of installed packages to get better
    // package descriptions for com.microsoft.Windows64 and similar packages
    if (job->shouldProceed()) {
        Job* sub = job->newSubJob(0.03,
                QObject::tr("Adding well-known packages"), true, true);
        AbstractThirdPartyPM* pm = new WellKnownProgramsThirdPartyPM(
                this->packageName);
        detect3rdParty(sub, rep, pm, false);
        delete pm;
    }

    timer.time(3);

    if (job->shouldProceed()) {
        Job* sub = job->newSubJob(0.02,
                QObject::tr("Setting the NPACKD_CL environment variable"));
        QString err = rep->updateNpackdCLEnvVar();
        if (!err.isEmpty())
            job->setErrorMessage(err);
        else
            sub->completeWithProgress();
    }

    timer.time(4);

    // qDebug() << "InstalledPackages::refresh.2";

    // detecting from the list of installed packages should happen first
    // as all other packages consult the list of installed packages. Secondly,
    // MSI or the programs from the control panel may be installed in strange
    // locations like "C:\" which "uninstalls" all packages installed by Npackd
    if (job->shouldProceed()) {
        Job* sub = job->newSubJob(0.01,
                QObject::tr("Reading the list of packages installed by Npackd"),
                true, true);

        AbstractThirdPartyPM* pm = new InstalledPackagesThirdPartyPM();
        detect3rdParty(sub, rep, pm, false);
        delete pm;
    }

     // qDebug() << "InstalledPackages::refresh.2.1";

/*
 * use DISM API instead
    if (job->shouldProceed()) {
        Job* sub = job->newSubJob(0.01,
                QObject::tr("Detecting Component Based Servicing packages"),
                true, true);

        AbstractThirdPartyPM* pm = new CBSThirdPartyPM();
        detect3rdParty(sub, rep, pm, true, "cbs:");
        delete pm;
    }
 */

    timer.time(5);

    if (job->shouldProceed()) {
        if (detectMSI) {
            Job* sub = job->newSubJob(0.05,
                    QObject::tr("Detecting MSI packages"), true, true);
            // MSI package detection should happen before the detection for
            // control panel programs
            AbstractThirdPartyPM* pm = new MSIThirdPartyPM();
            detect3rdParty(sub, rep, pm, true, "msi:");
            delete pm;
        } else {
            job->setProgress(job->getProgress() + 0.05);
        }
    }

    timer.time(6);

    // qDebug() << "InstalledPackages::refresh.3";

    if (job->shouldProceed()) {
        Job* sub = job->newSubJob(0.02,
                QObject::tr("Detecting software control panel packages"),
                true, true);

        AbstractThirdPartyPM* pm = new ControlPanelThirdPartyPM();
        detect3rdParty(sub, rep, pm, true, "control-panel:");
        delete pm;
    }

    timer.time(7);

    if (job->shouldProceed()) {
        Job* sub = job->newSubJob(0.05,
                QObject::tr("Clearing information about installed package versions in nested directories"));
        QString err = clearPackagesInNestedDirectories();
        if (!err.isEmpty())
            job->setErrorMessage(err);
        else {
            sub->completeWithProgress();
            job->setProgress(1);
        }
    }

    timer.time(8);

    // timer.dump();

    // this->mutex.unlock();

    job->complete();
}
void InstalledPackages::detect3rdParty(Job* job, DBRepository* r,
        Repository* rep,
        const QList<InstalledPackageVersion*>& installed,
        bool replace, const QString& detectionInfoPrefix)
{
    // this method does not manipulate "date" directly => no locking

    HRTimer timer(5);
    timer.time(0);

    timer.time(1);

    // qDebug() << "detect3rdParty 3";

    // remove packages and versions that are not installed
    // we assume that one 3rd party package manager does not create package
    // or package version objects for another
    if (job->shouldProceed()) {
        QSet<QString> packages;
        for (int i = 0; i < installed.size();i++) {
            InstalledPackageVersion* ipv = installed.at(i);
            packages.insert(ipv->package);
        }

        for (int i = 0; i < rep->packages.size(); ) {
            Package* p = rep->packages.at(i);
            if (!packages.contains(p->name)) {
                rep->packages.removeAt(i);
                rep->package2versions.remove(p->name);
                delete p;
            } else
                i++;
        }

        for (int i = 0; i < rep->packageVersions.size(); ) {
            PackageVersion* pv = rep->packageVersions.at(i);
            if (!packages.contains(pv->package)) {
                rep->packageVersions.removeAt(i);
                delete pv;
            } else
                i++;
        }
    }

    // save all detected packages and versions
    if (job->shouldProceed()) {
        Job* sub = job->newSubJob(0.2, QObject::tr("Saving"), true, true);
        r->saveAll(sub, rep, replace);
    }

    timer.time(2);

    // remove all package versions that are not detected as installed anymore
    if (job->shouldProceed() && !detectionInfoPrefix.isEmpty()) {
        QSet<QString> installedPVs;
        for (int i = 0; i < installed.count(); i++) {
            InstalledPackageVersion* ipv = installed.at(i);
            installedPVs.insert(ipv->package + "@" +
                    ipv->version.getVersionString());
        }

        // remove uninstalled packages
        QList<InstalledPackageVersion*> ipvs = getAll();
        for (int i = 0; i < ipvs.count(); i++) {
            InstalledPackageVersion* ipv = ipvs.at(i);
            bool same3rdPartyPM = ipv->detectionInfo.indexOf(
                    detectionInfoPrefix) == 0 ||
                    (detectionInfoPrefix == "msi:" &&
                    ipv->package.startsWith("msi.")) ||
                    (detectionInfoPrefix == "control-panel:" &&
                    ipv->package.startsWith("control-panel."));
            if (same3rdPartyPM &&
                    ipv->installed() && !installedPVs.contains(
                    ipv->package + "@" +
                    ipv->version.getVersionString())) {
                this->setPackageVersionPath(ipv->package, ipv->version, "");
            }
        }
        qDeleteAll(ipvs);
    }

    QStringList packagePaths = this->getAllInstalledPackagePaths();
    for (int i = 0; i < packagePaths.size(); i++) {
        packagePaths[i] = WPMUtils::normalizePath(packagePaths.at(i));
    }

    // qDebug() << "InstalledPackages::detect3rdParty.0";

    if (job->shouldProceed()) {
        for (int i = 0; i < installed.count(); i++) {
            InstalledPackageVersion* ipv = installed.at(i);

            // qDebug() << ipv->package << ipv->version.getVersionString();

            // if the package version is already installed, we skip it
            InstalledPackageVersion* existing = find(ipv->package,
                    ipv->version);
            if (existing && existing->installed()) {
                // qDebug() << "existing: " << existing->toString();
                delete existing;
                continue;
            }

            delete existing;

            // qDebug() << "    0.1";

            // we cannot handle nested directories
            QString path = ipv->directory;
            if (!path.isEmpty()) {
                path = WPMUtils::normalizePath(path);
                if (WPMUtils::isUnderOrEquals(path, packagePaths))
                    continue;
            }

            // qDebug() << "    0.2";

            processOneInstalled3rdParty(r, ipv);
        }
    }

    // qDebug() << "detect3rdParty 5";

    timer.time(3);

    timer.time(4);

    // qDebug() << detectionInfoPrefix;

    // timer.dump();

    job->complete();
}