root/src/apps/packageinstaller/UninstallView.cpp
/*
 * Copyright (c) 2007-2010, Haiku, Inc.
 * Distributed under the terms of the MIT license.
 *
 * Author:
 *              Ɓukasz 'Sil2100' Zemczak <sil2100@vexillium.org>
 */


#include "UninstallView.h"

#include <stdio.h>
#include <string.h>

#include <Alert.h>
#include <Box.h>
#include <Button.h>
#include <Catalog.h>
#include <ControlLook.h>
#include <Directory.h>
#include <Entry.h>
#include <File.h>
#include <FilePanel.h>
#include <FindDirectory.h>
#include <LayoutBuilder.h>
#include <ListView.h>
#include <Locale.h>
#include <NodeMonitor.h>
#include <ScrollView.h>
#include <SeparatorView.h>
#include <String.h>
#include <StringView.h>
#include <SpaceLayoutItem.h>
#include <TextView.h>

#include "main.h"


#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "UninstallView"


enum {
        P_MSG_INSTALL = 'umin',
        P_MSG_REMOVE = 'umrm',
        P_MSG_SELECT
};


// TODO list:
//      - B_ENTRY_MOVED
//      - Right now the installed package info naming convention is the same
//              as at SoftwareValet. Maybe there would be a better one?
//      - Add a status window (reuse the one from PackageInstall)


class UninstallView::InfoItem : public BStringItem {
public:
        InfoItem(const BString& name, const BString& version,
                        const char* filename, const node_ref& ref)
                :
                BStringItem(name.String()),
                fName(name),
                fVersion(version),
                fNodeRef(ref)
        {
                if (fName.Length() == 0)
                        SetText(filename);
        }

        const char* GetName() { return fName.String(); }
        const char* GetVersion() { return fVersion.String(); };
        node_ref GetNodeRef() { return fNodeRef; };

private:
        BString         fName;
        BString         fVersion;
        node_ref        fNodeRef;
};




UninstallView::UninstallView()
        :
        BGroupView(B_VERTICAL),
        fOpenPanel(new BFilePanel(B_OPEN_PANEL))
{
        fNoPackageSelectedString = B_TRANSLATE("No package selected.");
        _InitView();
}


UninstallView::~UninstallView()
{
        // Stop all node watching
        stop_watching(this);
}


void
UninstallView::AttachedToWindow()
{
        fAppList->SetTarget(this);
        fInstallButton->SetTarget(this);
        fRemoveButton->SetTarget(this);

        _ReloadAppList();

        // We loaded the list, but now let's set up a node watcher for the packages
        // directory, so that we can update the list of installed packages in real
        // time
        _CachePathToPackages();
        node_ref ref;
        fWatcherRunning = false;
        BDirectory dir(fToPackages.Path());
        if (dir.InitCheck() != B_OK) {
                // The packages/ directory obviously does not exist.
                // Since this is the case, we need to watch for it to appear first

                BPath path;
                fToPackages.GetParent(&path);
                if (dir.SetTo(path.Path()) != B_OK)
                        return;
        } else
                fWatcherRunning = true;

        dir.GetNodeRef(&ref);

        if (watch_node(&ref, B_WATCH_DIRECTORY, this) != B_OK) {
                fWatcherRunning = false;
                return;
        }
}


void
UninstallView::MessageReceived(BMessage* msg)
{
        switch (msg->what) {
                case B_NODE_MONITOR:
                {
                        int32 opcode;
                        if (msg->FindInt32("opcode", &opcode) != B_OK)
                                break;

                        fprintf(stderr, "Got an opcoded node monitor message\n");
                        if (opcode == B_ENTRY_CREATED) {
                                fprintf(stderr, "Created?...\n");
                                BString filename, name, version;
                                node_ref ref;
                                if (msg->FindString("name", &filename) != B_OK
                                        || msg->FindInt32("device", &ref.device) != B_OK
                                        || msg->FindInt64("node", &ref.node) != B_OK)
                                        break;

                                // TODO: This obviously is a hack
                                // The node watcher informs the view a bit to early, and
                                // because of this the data of the node is not ready at this
                                // moment. For this reason, we must give the filesystem some
                                // time before continuing.
                                usleep(10000);

                                if (fWatcherRunning) {
                                        _AddFile(filename.String(), ref);
                                } else {
                                        // This most likely means we were waiting for
                                        // the packages/ dir to appear
                                        if (filename == "packages") {
                                                if (watch_node(&ref, B_WATCH_DIRECTORY, this) == B_OK)
                                                        fWatcherRunning = true;
                                        }
                                }
                        } else if (opcode == B_ENTRY_REMOVED) {
                                node_ref ref;
                                if (msg->FindInt32("device", &ref.device) != B_OK
                                        || msg->FindInt64("node", &ref.node) != B_OK)
                                        break;

                                int32 i, count = fAppList->CountItems();
                                InfoItem* iter;
                                for (i = 0; i < count; i++) {
                                        iter = static_cast<InfoItem *>(fAppList->ItemAt(i));
                                        if (iter->GetNodeRef() == ref) {
                                                if (i == fAppList->CurrentSelection())
                                                        fDescription->SetText(fNoPackageSelectedString);
                                                fAppList->RemoveItem(i);
                                                delete iter;
                                        }
                                }
                        } else if (opcode == B_ENTRY_MOVED) {
                                ino_t from, to;
                                if (msg->FindInt64("from directory", &from) != B_OK
                                        || msg->FindInt64("to directory", &to) != B_OK)
                                        break;

                                BDirectory packagesDir(fToPackages.Path());
                                node_ref ref;
                                packagesDir.GetNodeRef(&ref);

                                if (ref.node == to) {
                                        // Package added
                                        // TODO
                                } else if (ref.node == from) {
                                        // Package removed
                                        // TODO
                                }
                        }
                        break;
                }
                case P_MSG_SELECT:
                {
                        fRemoveButton->SetEnabled(false);
                        fDescription->SetText(fNoPackageSelectedString);

                        int32 index = fAppList->CurrentSelection();
                        if (index < 0)
                                break;

                        fprintf(stderr, "Another debug message...\n");

                        InfoItem* item = dynamic_cast<InfoItem*>(fAppList->ItemAt(index));
                        if (!item)
                                break;

                        fprintf(stderr, "Uh: %s and %s\n", item->GetName(),
                                item->GetVersion());

                        if (fCurrentSelection.SetTo(item->GetName(),
                                        item->GetVersion()) != B_OK)
                                break;

                        fRemoveButton->SetEnabled(true);
                        fDescription->SetText(fCurrentSelection.Description());
                        break;
                }
                case P_MSG_INSTALL:
                {
                        fOpenPanel->Show();
                        break;
                }
                case P_MSG_REMOVE:
                {
                        if (fCurrentSelection.InitCheck() != B_OK)
                                break;

                        int32 index = fAppList->CurrentSelection();
                        if (index < 0)
                                break;

                        BAlert* notify;
                        if (fCurrentSelection.Uninstall() == B_OK) {
                                BListItem* item = fAppList->RemoveItem(index);
                                delete item;

                                fDescription->SetText(fNoPackageSelectedString);

                                notify = new BAlert("removal_success",
                                        B_TRANSLATE("The package you selected has been "
                                        "successfully removed from your system."),
                                        B_TRANSLATE("OK"));
                        } else {
                                notify = new BAlert("removal_failed",
                                        B_TRANSLATE(
                                        "The selected package was not removed from your system. "
                                        "The given installed package information file might have "
                                        "been corrupted."), B_TRANSLATE("OK"), NULL,
                                        NULL, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
                        }
                        notify->SetFlags(notify->Flags() | B_CLOSE_ON_ESCAPE);
                        notify->Go();
                        break;
                }
                default:
                        BView::MessageReceived(msg);
                        break;
        }
}


void
UninstallView::RefsReceived(BMessage* message)
{
        static_cast<PackageInstaller*>(be_app)->RefsReceived(message);
}


void
UninstallView::_InitView()
{
        SetViewUIColor(B_PANEL_BACKGROUND_COLOR);

        fAppList = new BListView("pkg_list", B_SINGLE_SELECTION_LIST);
        fAppList->SetSelectionMessage(new BMessage(P_MSG_SELECT));
        BScrollView* scrollView = new BScrollView("list_scroll", fAppList,
                0, false, true, B_NO_BORDER);

        BStringView* descriptionLabel = new BStringView("desc_label",
                B_TRANSLATE("Package description"));
        descriptionLabel->SetFont(be_bold_font);

        fDescription = new BTextView("description", B_WILL_DRAW);
        fDescription->MakeSelectable(false);
        fDescription->MakeEditable(false);
        fDescription->SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
        fDescription->SetText(fNoPackageSelectedString);

        fInstallButton = new BButton("install", B_TRANSLATE("Install" B_UTF8_ELLIPSIS),
                new BMessage(P_MSG_INSTALL));
        fRemoveButton = new BButton("removal", B_TRANSLATE("Remove"),
                new BMessage(P_MSG_REMOVE));
        fRemoveButton->SetEnabled(false);

        const float spacing = be_control_look->DefaultItemSpacing();

        BLayoutBuilder::Group<>(this, B_VERTICAL, 0)
                .Add(scrollView, 10)
                .Add(new BSeparatorView(B_HORIZONTAL, B_PLAIN_BORDER))
                .AddGroup(B_VERTICAL)
                        .SetInsets(spacing)
                        .AddGroup(B_HORIZONTAL, 0)
                                .Add(descriptionLabel)
                                .AddGlue()
                        .End()
                        .AddGroup(B_HORIZONTAL, 0)
                                .Add(BSpaceLayoutItem::CreateHorizontalStrut(10))
                                .Add(fDescription)
                        .End()
                .End()
                .Add(new BSeparatorView(B_HORIZONTAL, B_PLAIN_BORDER))
                .AddGroup(B_HORIZONTAL)
                        .SetInsets(spacing)
                        .AddGlue()
                        .Add(fInstallButton)
                        .Add(fRemoveButton)
                .End()
        .End();
}


status_t
UninstallView::_ReloadAppList()
{
        _ClearAppList();

        if (fToPackages.InitCheck() != B_OK)
                _CachePathToPackages();

        BDirectory dir(fToPackages.Path());
        status_t ret = dir.InitCheck();
        if (ret != B_OK)
                return ret;

        BEntry iter;
        while (dir.GetNextEntry(&iter) == B_OK) {
                char filename[B_FILE_NAME_LENGTH];
                if (iter.GetName(filename) != B_OK)
                        continue;

                node_ref ref;
                if (iter.GetNodeRef(&ref) != B_OK)
                        continue;

                BString filenameString(filename);
                if (!filenameString.IEndsWith(".pdb")) {
                        printf("Ignoring non-package '%s'\n", filename);
                        continue;
                }

                printf("Found package '%s'\n", filename);
                _AddFile(filename, ref);
        }

        if (ret != B_ENTRY_NOT_FOUND)
                return ret;

        return B_OK;
}


void
UninstallView::_ClearAppList()
{
        while (BListItem* item = fAppList->RemoveItem((int32)0))
                delete item;
}


void
UninstallView::_AddFile(const char* filename, const node_ref& ref)
{
        BString name;
        status_t ret = info_get_package_name(filename, name);
        if (ret != B_OK || name.Length() == 0)
                fprintf(stderr, "Error extracting package name: %s\n", strerror(ret));
        BString version;
        ret = info_get_package_version(filename, version);
        if (ret != B_OK || version.Length() == 0) {
                fprintf(stderr, "Error extracting package version: %s\n",
                        strerror(ret));
        }
        fAppList->AddItem(new InfoItem(name, version, filename, ref));
}


void
UninstallView::_CachePathToPackages()
{
        if (find_directory(B_USER_CONFIG_DIRECTORY, &fToPackages) != B_OK)
                return;
        if (fToPackages.Append(kPackagesDir) != B_OK)
                return;
}