root/src/apps/softwareupdater/CheckManager.cpp
/*
 * Copyright 2013-2017, Haiku, Inc. All Rights Reserved.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *              Axel Dörfler <axeld@pinc-software.de>
 *              Rene Gollent <rene@gollent.com>
 *              Ingo Weinhold <ingo_weinhold@gmx.de>
 *              Brian Hill <supernova@tycho.email>
 */


#include "CheckManager.h"

#include <sys/ioctl.h>
#include <unistd.h>

#include <Application.h>
#include <Catalog.h>
#include <Message.h>
#include <Messenger.h>
#include <NetworkInterface.h>
#include <NetworkRoster.h>
#include <NodeInfo.h>
#include <Notification.h>
#include <Roster.h>
#include <StringFormat.h>

#include <package/manager/Exceptions.h>
#include <package/solver/SolverPackage.h>
#include <package/solver/SolverProblem.h>
#include <package/solver/SolverProblemSolution.h>

#include "constants.h"

using namespace BPackageKit;
using namespace BPackageKit::BManager::BPrivate;

#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "CheckManager"


CheckManager::CheckManager(BPackageInstallationLocation location,
        bool verbose)
        :
        BPackageManager(location, &fClientInstallationInterface, this),
        BPackageManager::UserInteractionHandler(),
        fClientInstallationInterface(),
        fVerbose(verbose),
        fNotificationId("")
{
        if (verbose) {
                app_info info;
                if (be_app->GetAppInfo(&info)!= B_OK)
                        fVerbose = false;
                else
                        fNotificationId << info.team;
        }
        fHeaderChecking = B_TRANSLATE("Checking for updates");
        fTextContacting = B_TRANSLATE("Contacting software repositories to check "
                        "for package updates.");
}


void
CheckManager::CheckNetworkConnection()
{
        BNetworkRoster& roster = BNetworkRoster::Default();
        BNetworkInterface interface;
        uint32 cookie = 0;
        while (roster.GetNextInterface(&cookie, interface) == B_OK) {
                uint32 flags = interface.Flags();
                if ((flags & IFF_LOOPBACK) == 0
                        && (flags & (IFF_UP | IFF_LINK)) == (IFF_UP | IFF_LINK)) {
                        return;
                }
        }

        // No network connection detected, cannot continue
        fputs(B_TRANSLATE("No active network connection was found.\n"), stderr);
        throw BAbortedByUserException();
}


void
CheckManager::JobFailed(BSupportKit::BJob* job)
{
        BString error = job->ErrorString();
        if (error.Length() > 0) {
                error.ReplaceAll("\n", "\n*** ");
                fprintf(stderr, "%s", error.String());
        }
}


void
CheckManager::JobAborted(BSupportKit::BJob* job)
{
        puts("Job aborted");
        BString error = job->ErrorString();
        if (error.Length() > 0) {
                error.ReplaceAll("\n", "\n*** ");
                fprintf(stderr, "%s", error.String());
        }
}


void
CheckManager::NoUpdatesNotification()
{
        if (fVerbose) {
                BString header = B_TRANSLATE("No updates available");
                BString text = B_TRANSLATE("There were no updates found.");
                _SendNotification(header.String(), text.String());
        }
}


void
CheckManager::HandleProblems()
{
        puts("Encountered problems:");

        int32 problemCount = fSolver->CountProblems();
        for (int32 i = 0; i < problemCount; i++) {
                BSolverProblem* problem = fSolver->ProblemAt(i);
                printf("problem %" B_PRId32 ": %s\n", i + 1,
                        problem->ToString().String());
        }

        BString title(B_TRANSLATE("Available updates found"));
        BString text(B_TRANSLATE("Click here to run %appname%. Some updates "
                "will require a problem solution to be selected."));
        text.ReplaceFirst("%appname%", B_TRANSLATE_SYSTEM_NAME("SoftwareUpdater"));
        _SendNotification(title, text);

        throw BAbortedByUserException();
}


void
CheckManager::ConfirmChanges(bool fromMostSpecific)
{
        int32 count = fInstalledRepositories.CountItems();
        int32 updateCount = 0;

        if (fromMostSpecific) {
                for (int32 i = count - 1; i >= 0; i--)
                        _CountUpdates(*fInstalledRepositories.ItemAt(i), updateCount);
        } else {
                for (int32 i = 0; i < count; i++)
                        _CountUpdates(*fInstalledRepositories.ItemAt(i), updateCount);
        }

        printf("Update count=%" B_PRId32 "\n", updateCount);
        if (updateCount > 0) {
                BString title;
                static BStringFormat formatTitle(B_TRANSLATE(
                        "Software {0, plural, one{update} other{updates}} available"));
                formatTitle.Format(title, updateCount);
                BString text;
                static BStringFormat formatText(B_TRANSLATE("Click here to "
                        "install {0, plural, one{# updated package} "
                        "other{# updated packages}}."));
                formatText.Format(text, updateCount);

                _SendNotification(title.String(), text.String());
        }
        throw BAbortedByUserException();
}


void
CheckManager::Warn(status_t error, const char* format, ...)
{
        va_list args;
        va_start(args, format);
        vfprintf(stderr, format, args);
        va_end(args);

        if (error == B_OK)
                puts("");
        else
                printf(": %s\n", strerror(error));
}


void
CheckManager::ProgressPackageDownloadStarted(const char* packageName)
{
        if (fVerbose)
                _SendNotification(fHeaderChecking.String(), fTextContacting.String());

        printf("Downloading %s...\n", packageName);
}


void
CheckManager::ProgressPackageDownloadActive(const char* packageName,
        float completionPercentage, off_t bytes, off_t totalBytes)
{
        static const char* progressChars[] = {
                "\xE2\x96\x8F",
                "\xE2\x96\x8E",
                "\xE2\x96\x8D",
                "\xE2\x96\x8C",
                "\xE2\x96\x8B",
                "\xE2\x96\x8A",
                "\xE2\x96\x89",
                "\xE2\x96\x88",
        };

        int width = 70;

        struct winsize winSize;
        if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &winSize) == 0
                && winSize.ws_col < 77) {
                // We need 7 characters for the percent display
                width = winSize.ws_col - 7;
        }

        int position;
        int ipart = (int)(completionPercentage * width);
        int fpart = (int)(((completionPercentage * width) - ipart) * 8);

        fputs("\r", stdout); // return to the beginning of the line

        for (position = 0; position < width; position++) {
                if (position < ipart) {
                        // This part is fully downloaded, show a full block
                        fputs(progressChars[7], stdout);
                } else if (position > ipart) {
                        // This part is not downloaded, show a space
                        fputs(" ", stdout);
                } else {
                        // This part is partially downloaded
                        fputs(progressChars[fpart], stdout);
                }
        }

        // Also print the progress percentage
        printf(" %3d%%", (int)(completionPercentage * 100));

        fflush(stdout);

}


void
CheckManager::ProgressPackageDownloadComplete(const char* packageName)
{
        // Overwrite the progress bar with whitespace
        fputs("\r", stdout);
        struct winsize w;
        ioctl(STDOUT_FILENO, TIOCGWINSZ, &w);
        for (int i = 0; i < (w.ws_col); i++)
                fputs(" ", stdout);
        fputs("\r\x1b[1A", stdout); // Go to previous line.

        printf("Downloading %s...done.\n", packageName);
}


void
CheckManager::ProgressPackageChecksumStarted(const char* title)
{
        if (fVerbose)
                _SendNotification(fHeaderChecking.String(), title);

        printf("%s...", title);
}


void
CheckManager::ProgressPackageChecksumComplete(const char* title)
{
        puts("done.");
}


void
CheckManager::_CountUpdates(InstalledRepository& installationRepository,
        int32& updateCount)
{
        if (!installationRepository.HasChanges())
                return;

        PackageList& packagesToActivate
                = installationRepository.PackagesToActivate();
        PackageList& packagesToDeactivate
                = installationRepository.PackagesToDeactivate();

        for (int32 i = 0;
                BSolverPackage* installPackage = packagesToActivate.ItemAt(i);
                i++) {
                for (int32 j = 0;
                        BSolverPackage* uninstallPackage = packagesToDeactivate.ItemAt(j);
                        j++) {
                        if (installPackage->Info().Name() == uninstallPackage->Info().Name()) {
                                updateCount++;
                                break;
                        }
                }
        }
}


void
CheckManager::_SendNotification(const char* title, const char* text)
{
        BNotification notification(B_INFORMATION_NOTIFICATION);
        notification.SetGroup(B_TRANSLATE_SYSTEM_NAME("SoftwareUpdater"));
        notification.SetTitle(title);
        notification.SetContent(text);
        notification.SetOnClickApp(kAppSignature);
        if(fVerbose)
                notification.SetMessageID(fNotificationId);
        notification.Send();
}