/*
* Copyright 2015, Axel Dörfler, <axeld@pinc-software.de>.
* Copyright 2013-2014, Stephan Aßmus <superstippi@gmx.de>.
* Copyright 2013, Rene Gollent, rene@gollent.com.
* Copyright 2013, Ingo Weinhold, ingo_weinhold@gmx.de.
* Copyright 2016-2019, Andrew Lindesay <apl@lindesay.co.nz>.
* Copyright 2017, Julian Harnath <julian.harnath@rwth-aachen.de>.
* All rights reserved. Distributed under the terms of the MIT License.
*/
#include "MainWindow.h"
#include <map>
#include <vector>
#include <stdio.h>
#include <Alert.h>
#include <Autolock.h>
#include <Application.h>
#include <Button.h>
#include <Catalog.h>
#include <CardLayout.h>
#include <LayoutBuilder.h>
#include <MenuBar.h>
#include <MenuItem.h>
#include <Messenger.h>
#include <Roster.h>
#include <Screen.h>
#include <ScrollView.h>
#include <StringList.h>
#include <StringView.h>
#include <TabView.h>
#include "AppUtils.h"
#include "AutoDeleter.h"
#include "AutoLocker.h"
#include "DecisionProvider.h"
#include "FeaturedPackagesView.h"
#include "FilterView.h"
#include "Logger.h"
#include "PackageInfoView.h"
#include "PackageListView.h"
#include "PackageManager.h"
#include "ProcessCoordinator.h"
#include "ProcessCoordinatorFactory.h"
#include "RatePackageWindow.h"
#include "support.h"
#include "ScreenshotWindow.h"
#include "UserLoginWindow.h"
#include "WorkStatusView.h"
#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "MainWindow"
enum {
MSG_BULK_LOAD_DONE = 'mmwd',
MSG_REFRESH_REPOS = 'mrrp',
MSG_MANAGE_REPOS = 'mmrp',
MSG_SOFTWARE_UPDATER = 'mswu',
MSG_LOG_IN = 'lgin',
MSG_LOG_OUT = 'lgot',
MSG_AUTHORIZATION_CHANGED = 'athc',
MSG_CATEGORIES_LIST_CHANGED = 'clic',
MSG_PACKAGE_CHANGED = 'pchd',
MSG_WORK_STATUS_CHANGE = 'wsch',
MSG_WORK_STATUS_CLEAR = 'wscl',
MSG_SHOW_FEATURED_PACKAGES = 'sofp',
MSG_SHOW_AVAILABLE_PACKAGES = 'savl',
MSG_SHOW_INSTALLED_PACKAGES = 'sins',
MSG_SHOW_SOURCE_PACKAGES = 'ssrc',
MSG_SHOW_DEVELOP_PACKAGES = 'sdvl'
};
using namespace BPackageKit;
using namespace BPackageKit::BManager::BPrivate;
typedef std::map<BString, PackageInfoRef> PackageInfoMap;
struct RefreshWorkerParameters {
MainWindow* window;
bool forceRefresh;
RefreshWorkerParameters(MainWindow* window, bool forceRefresh)
:
window(window),
forceRefresh(forceRefresh)
{
}
};
class MainWindowModelListener : public ModelListener {
public:
MainWindowModelListener(const BMessenger& messenger)
:
fMessenger(messenger)
{
}
virtual void AuthorizationChanged()
{
if (fMessenger.IsValid())
fMessenger.SendMessage(MSG_AUTHORIZATION_CHANGED);
}
virtual void CategoryListChanged()
{
if (fMessenger.IsValid())
fMessenger.SendMessage(MSG_CATEGORIES_LIST_CHANGED);
}
private:
BMessenger fMessenger;
};
MainWindow::MainWindow(const BMessage& settings)
:
BWindow(BRect(50, 50, 650, 550), B_TRANSLATE_SYSTEM_NAME("HaikuDepot"),
B_DOCUMENT_WINDOW_LOOK, B_NORMAL_WINDOW_FEEL,
B_ASYNCHRONOUS_CONTROLS | B_AUTO_UPDATE_SIZE_LIMITS),
fScreenshotWindow(NULL),
fUserMenu(NULL),
fLogInItem(NULL),
fLogOutItem(NULL),
fModelListener(new MainWindowModelListener(BMessenger(this)), true),
fBulkLoadProcessCoordinator(NULL),
fSinglePackageMode(false)
{
BMenuBar* menuBar = new BMenuBar("Main Menu");
_BuildMenu(menuBar);
BMenuBar* userMenuBar = new BMenuBar("User Menu");
_BuildUserMenu(userMenuBar);
set_small_font(userMenuBar);
userMenuBar->SetExplicitMaxSize(BSize(B_SIZE_UNSET,
menuBar->MaxSize().height));
fFilterView = new FilterView();
fFeaturedPackagesView = new FeaturedPackagesView();
fPackageListView = new PackageListView(fModel.Lock());
fPackageInfoView = new PackageInfoView(fModel.Lock(), this);
fSplitView = new BSplitView(B_VERTICAL, 5.0f);
fWorkStatusView = new WorkStatusView("work status");
fPackageListView->AttachWorkStatusView(fWorkStatusView);
fListTabs = new TabView(BMessenger(this),
BMessage(MSG_SHOW_FEATURED_PACKAGES), "list tabs");
fListTabs->AddTab(fFeaturedPackagesView);
fListTabs->AddTab(fPackageListView);
BLayoutBuilder::Group<>(this, B_VERTICAL, 0.0f)
.AddGroup(B_HORIZONTAL, 0.0f)
.Add(menuBar, 1.0f)
.Add(userMenuBar, 0.0f)
.End()
.Add(fFilterView)
.AddSplit(fSplitView)
.AddGroup(B_VERTICAL)
.Add(fListTabs)
.SetInsets(
B_USE_DEFAULT_SPACING, 0.0f,
B_USE_DEFAULT_SPACING, 0.0f)
.End()
.Add(fPackageInfoView)
.End()
.Add(fWorkStatusView)
;
fSplitView->SetCollapsible(0, false);
fSplitView->SetCollapsible(1, false);
fModel.AddListener(fModelListener);
// Restore settings
BMessage columnSettings;
if (settings.FindMessage("column settings", &columnSettings) == B_OK)
fPackageListView->LoadState(&columnSettings);
bool showOption;
if (settings.FindBool("show featured packages", &showOption) == B_OK)
fModel.SetShowFeaturedPackages(showOption);
if (settings.FindBool("show available packages", &showOption) == B_OK)
fModel.SetShowAvailablePackages(showOption);
if (settings.FindBool("show installed packages", &showOption) == B_OK)
fModel.SetShowInstalledPackages(showOption);
if (settings.FindBool("show develop packages", &showOption) == B_OK)
fModel.SetShowDevelopPackages(showOption);
if (settings.FindBool("show source packages", &showOption) == B_OK)
fModel.SetShowSourcePackages(showOption);
if (fModel.ShowFeaturedPackages())
fListTabs->Select(0);
else
fListTabs->Select(1);
_RestoreUserName(settings);
_RestoreWindowFrame(settings);
atomic_set(&fPackagesToShowListID, 0);
// start worker threads
BPackageRoster().StartWatching(this,
B_WATCH_PACKAGE_INSTALLATION_LOCATIONS);
_StartBulkLoad();
_InitWorkerThreads();
}
MainWindow::MainWindow(const BMessage& settings, const PackageInfoRef& package)
:
BWindow(BRect(50, 50, 650, 350), B_TRANSLATE_SYSTEM_NAME("HaikuDepot"),
B_DOCUMENT_WINDOW_LOOK, B_NORMAL_WINDOW_FEEL,
B_ASYNCHRONOUS_CONTROLS | B_AUTO_UPDATE_SIZE_LIMITS),
fWorkStatusView(NULL),
fScreenshotWindow(NULL),
fUserMenu(NULL),
fLogInItem(NULL),
fLogOutItem(NULL),
fModelListener(new MainWindowModelListener(BMessenger(this)), true),
fBulkLoadProcessCoordinator(NULL),
fSinglePackageMode(true)
{
fFilterView = new FilterView();
fPackageListView = new PackageListView(fModel.Lock());
fPackageInfoView = new PackageInfoView(fModel.Lock(), this);
BLayoutBuilder::Group<>(this, B_VERTICAL)
.Add(fPackageInfoView)
.SetInsets(0, B_USE_WINDOW_INSETS, 0, 0)
;
fModel.AddListener(fModelListener);
// Restore settings
_RestoreUserName(settings);
_RestoreWindowFrame(settings);
fPackageInfoView->SetPackage(package);
_InitWorkerThreads();
}
MainWindow::~MainWindow()
{
BPackageRoster().StopWatching(this);
delete_sem(fPendingActionsSem);
if (fPendingActionsWorker >= 0)
wait_for_thread(fPendingActionsWorker, NULL);
delete_sem(fPackageToPopulateSem);
if (fPopulatePackageWorker >= 0)
wait_for_thread(fPopulatePackageWorker, NULL);
delete_sem(fNewPackagesToShowSem);
delete_sem(fShowPackagesAcknowledgeSem);
if (fShowPackagesWorker >= 0)
wait_for_thread(fShowPackagesWorker, NULL);
if (fScreenshotWindow != NULL && fScreenshotWindow->Lock())
fScreenshotWindow->Quit();
}
bool
MainWindow::QuitRequested()
{
BMessage settings;
StoreSettings(settings);
BMessage message(MSG_MAIN_WINDOW_CLOSED);
message.AddMessage(KEY_WINDOW_SETTINGS, &settings);
be_app->PostMessage(&message);
_StopBulkLoad();
return true;
}
void
MainWindow::MessageReceived(BMessage* message)
{
switch (message->what) {
case MSG_BULK_LOAD_DONE:
_BulkLoadCompleteReceived();
break;
case B_SIMPLE_DATA:
case B_REFS_RECEIVED:
// TODO: ?
break;
case B_PACKAGE_UPDATE:
// TODO: We should do a more selective update depending on the
// "event", "location", and "change count" fields!
_StartBulkLoad(false);
break;
case MSG_REFRESH_REPOS:
_StartBulkLoad(true);
break;
case MSG_WORK_STATUS_CHANGE:
_HandleWorkStatusChangeMessageReceived(message);
break;
case MSG_MANAGE_REPOS:
be_roster->Launch("application/x-vnd.Haiku-Repositories");
break;
case MSG_SOFTWARE_UPDATER:
be_roster->Launch("application/x-vnd.haiku-softwareupdater");
break;
case MSG_LOG_IN:
_OpenLoginWindow(BMessage());
break;
case MSG_LOG_OUT:
fModel.SetUsername("");
break;
case MSG_AUTHORIZATION_CHANGED:
_UpdateAuthorization();
break;
case MSG_CATEGORIES_LIST_CHANGED:
fFilterView->AdoptModel(fModel);
break;
case MSG_SHOW_FEATURED_PACKAGES:
// check to see if we aren't already on the current tab
if (fListTabs->Selection() ==
(fModel.ShowFeaturedPackages() ? 0 : 1))
break;
{
BAutolock locker(fModel.Lock());
fModel.SetShowFeaturedPackages(
fListTabs->Selection() == 0);
}
_AdoptModel();
break;
case MSG_SHOW_AVAILABLE_PACKAGES:
{
BAutolock locker(fModel.Lock());
fModel.SetShowAvailablePackages(
!fModel.ShowAvailablePackages());
}
_AdoptModel();
break;
case MSG_SHOW_INSTALLED_PACKAGES:
{
BAutolock locker(fModel.Lock());
fModel.SetShowInstalledPackages(
!fModel.ShowInstalledPackages());
}
_AdoptModel();
break;
case MSG_SHOW_SOURCE_PACKAGES:
{
BAutolock locker(fModel.Lock());
fModel.SetShowSourcePackages(!fModel.ShowSourcePackages());
}
_AdoptModel();
break;
case MSG_SHOW_DEVELOP_PACKAGES:
{
BAutolock locker(fModel.Lock());
fModel.SetShowDevelopPackages(!fModel.ShowDevelopPackages());
}
_AdoptModel();
break;
// this may be triggered by, for example, a user rating being added
// or having been altered.
case MSG_SERVER_DATA_CHANGED:
{
BString name;
if (message->FindString("name", &name) == B_OK) {
BAutolock locker(fModel.Lock());
if (fPackageInfoView->Package()->Name() == name) {
_PopulatePackageAsync(true);
} else {
if (Logger::IsDebugEnabled()) {
printf("pkg [%s] is updated on the server, but is "
"not selected so will not be updated.\n",
name.String());
}
}
}
break;
}
case MSG_PACKAGE_SELECTED:
{
BString name;
if (message->FindString("name", &name) == B_OK) {
BAutolock locker(fModel.Lock());
int count = fVisiblePackages.CountItems();
for (int i = 0; i < count; i++) {
const PackageInfoRef& package
= fVisiblePackages.ItemAtFast(i);
if (package.Get() != NULL && package->Name() == name) {
locker.Unlock();
_AdoptPackage(package);
break;
}
}
} else {
_ClearPackage();
}
break;
}
case MSG_CATEGORY_SELECTED:
{
BString code;
if (message->FindString("code", &code) != B_OK)
code = "";
{
BAutolock locker(fModel.Lock());
fModel.SetCategory(code);
}
_AdoptModel();
break;
}
case MSG_DEPOT_SELECTED:
{
BString name;
if (message->FindString("name", &name) != B_OK)
name = "";
{
BAutolock locker(fModel.Lock());
fModel.SetDepot(name);
}
_AdoptModel();
_UpdateAvailableRepositories();
break;
}
case MSG_SEARCH_TERMS_MODIFIED:
{
// TODO: Do this with a delay!
BString searchTerms;
if (message->FindString("search terms", &searchTerms) != B_OK)
searchTerms = "";
{
BAutolock locker(fModel.Lock());
fModel.SetSearchTerms(searchTerms);
}
_AdoptModel();
break;
}
case MSG_PACKAGE_CHANGED:
{
PackageInfo* info;
if (message->FindPointer("package", (void**)&info) == B_OK) {
PackageInfoRef ref(info, true);
uint32 changes;
if (message->FindUInt32("changes", &changes) != B_OK)
changes = 0;
if ((changes & PKG_CHANGED_STATE) != 0) {
BAutolock locker(fModel.Lock());
fModel.SetPackageState(ref, ref->State());
}
// Asynchronous updates to the package information
// can mean that the package needs to be added or
// removed to/from the visible packages when the current
// filter parameters are re-evaluated on this package.
bool wasVisible = fVisiblePackages.Contains(ref);
bool isVisible;
{
BAutolock locker(fModel.Lock());
// The package didn't get a chance yet to be in the
// visible package list
isVisible = fModel.MatchesFilter(ref);
// Transfer this single package, otherwise we miss
// other packages if they appear or disappear along
// with this one before receive a notification for
// them.
if (isVisible) {
fVisiblePackages.Add(ref);
} else if (wasVisible)
fVisiblePackages.Remove(ref);
}
if (wasVisible != isVisible) {
if (!isVisible) {
fPackageListView->RemovePackage(ref);
fFeaturedPackagesView->RemovePackage(ref);
} else {
fPackageListView->AddPackage(ref);
if (ref->IsProminent())
fFeaturedPackagesView->AddPackage(ref);
}
}
if (!fSinglePackageMode && (changes & PKG_CHANGED_STATE) != 0)
fWorkStatusView->PackageStatusChanged(ref);
}
break;
}
case MSG_RATE_PACKAGE:
_RatePackage();
break;
case MSG_SHOW_SCREENSHOT:
_ShowScreenshot();
break;
case MSG_PACKAGE_WORKER_BUSY:
{
BString reason;
status_t status = message->FindString("reason", &reason);
if (status != B_OK)
break;
if (!fSinglePackageMode)
fWorkStatusView->SetBusy(reason);
break;
}
case MSG_PACKAGE_WORKER_IDLE:
if (!fSinglePackageMode)
fWorkStatusView->SetIdle();
break;
case MSG_ADD_VISIBLE_PACKAGES:
{
struct SemaphoreReleaser {
SemaphoreReleaser(sem_id semaphore)
:
fSemaphore(semaphore)
{ }
~SemaphoreReleaser() { release_sem(fSemaphore); }
sem_id fSemaphore;
};
// Make sure acknowledge semaphore is released even on error,
// so the worker thread won't be blocked
SemaphoreReleaser acknowledger(fShowPackagesAcknowledgeSem);
int32 numPackages = 0;
type_code unused;
status_t status = message->GetInfo("package_ref", &unused,
&numPackages);
if (status != B_OK || numPackages == 0)
break;
int32 listID = 0;
status = message->FindInt32("list_id", &listID);
if (status != B_OK)
break;
if (listID != atomic_get(&fPackagesToShowListID)) {
// list is outdated, ignore
break;
}
for (int i = 0; i < numPackages; i++) {
PackageInfo* packageRaw = NULL;
status = message->FindPointer("package_ref", i,
(void**)&packageRaw);
if (status != B_OK)
break;
PackageInfoRef package(packageRaw, true);
fPackageListView->AddPackage(package);
if (package->IsProminent())
fFeaturedPackagesView->AddPackage(package);
}
break;
}
case MSG_UPDATE_SELECTED_PACKAGE:
{
const PackageInfoRef& selectedPackage = fPackageInfoView->Package();
fFeaturedPackagesView->SelectPackage(selectedPackage, true);
fPackageListView->SelectPackage(selectedPackage);
AutoLocker<BLocker> modelLocker(fModel.Lock());
if (!fVisiblePackages.Contains(fPackageInfoView->Package()))
fPackageInfoView->Clear();
break;
}
default:
BWindow::MessageReceived(message);
break;
}
}
void
MainWindow::StoreSettings(BMessage& settings) const
{
settings.AddRect(_WindowFrameName(), Frame());
if (!fSinglePackageMode) {
settings.AddRect("window frame", Frame());
BMessage columnSettings;
fPackageListView->SaveState(&columnSettings);
settings.AddMessage("column settings", &columnSettings);
settings.AddBool("show featured packages",
fModel.ShowFeaturedPackages());
settings.AddBool("show available packages",
fModel.ShowAvailablePackages());
settings.AddBool("show installed packages",
fModel.ShowInstalledPackages());
settings.AddBool("show develop packages", fModel.ShowDevelopPackages());
settings.AddBool("show source packages", fModel.ShowSourcePackages());
}
settings.AddString("username", fModel.Username());
}
void
MainWindow::PackageChanged(const PackageInfoEvent& event)
{
uint32 watchedChanges = PKG_CHANGED_STATE | PKG_CHANGED_PROMINENCE;
if ((event.Changes() & watchedChanges) != 0) {
PackageInfoRef ref(event.Package());
BMessage message(MSG_PACKAGE_CHANGED);
message.AddPointer("package", ref.Get());
message.AddUInt32("changes", event.Changes());
ref.Detach();
// reference needs to be released by MessageReceived();
PostMessage(&message);
}
}
status_t
MainWindow::SchedulePackageActions(PackageActionList& list)
{
AutoLocker<BLocker> lock(&fPendingActionsLock);
for (int32 i = 0; i < list.CountItems(); i++) {
if (!fPendingActions.Add(list.ItemAtFast(i)))
return B_NO_MEMORY;
}
return release_sem_etc(fPendingActionsSem, list.CountItems(), 0);
}
Model*
MainWindow::GetModel()
{
return &fModel;
}
void
MainWindow::_BuildMenu(BMenuBar* menuBar)
{
BMenu* menu = new BMenu(B_TRANSLATE("Tools"));
fRefreshRepositoriesItem = new BMenuItem(
B_TRANSLATE("Refresh repositories"), new BMessage(MSG_REFRESH_REPOS));
menu->AddItem(fRefreshRepositoriesItem);
menu->AddItem(new BMenuItem(B_TRANSLATE("Manage repositories"
B_UTF8_ELLIPSIS), new BMessage(MSG_MANAGE_REPOS)));
menu->AddItem(new BMenuItem(B_TRANSLATE("Check for updates"
B_UTF8_ELLIPSIS), new BMessage(MSG_SOFTWARE_UPDATER)));
menuBar->AddItem(menu);
fRepositoryMenu = new BMenu(B_TRANSLATE("Repositories"));
menuBar->AddItem(fRepositoryMenu);
menu = new BMenu(B_TRANSLATE("Show"));
fShowAvailablePackagesItem = new BMenuItem(
B_TRANSLATE("Available packages"),
new BMessage(MSG_SHOW_AVAILABLE_PACKAGES));
menu->AddItem(fShowAvailablePackagesItem);
fShowInstalledPackagesItem = new BMenuItem(
B_TRANSLATE("Installed packages"),
new BMessage(MSG_SHOW_INSTALLED_PACKAGES));
menu->AddItem(fShowInstalledPackagesItem);
menu->AddSeparatorItem();
fShowDevelopPackagesItem = new BMenuItem(
B_TRANSLATE("Develop packages"),
new BMessage(MSG_SHOW_DEVELOP_PACKAGES));
menu->AddItem(fShowDevelopPackagesItem);
fShowSourcePackagesItem = new BMenuItem(
B_TRANSLATE("Source packages"),
new BMessage(MSG_SHOW_SOURCE_PACKAGES));
menu->AddItem(fShowSourcePackagesItem);
menuBar->AddItem(menu);
}
void
MainWindow::_BuildUserMenu(BMenuBar* menuBar)
{
fUserMenu = new BMenu(B_TRANSLATE("Not logged in"));
fLogInItem = new BMenuItem(B_TRANSLATE("Log in" B_UTF8_ELLIPSIS),
new BMessage(MSG_LOG_IN));
fUserMenu->AddItem(fLogInItem);
fLogOutItem = new BMenuItem(B_TRANSLATE("Log out"),
new BMessage(MSG_LOG_OUT));
fUserMenu->AddItem(fLogOutItem);
menuBar->AddItem(fUserMenu);
}
void
MainWindow::_RestoreUserName(const BMessage& settings)
{
BString username;
if (settings.FindString("username", &username) == B_OK
&& username.Length() > 0) {
fModel.SetUsername(username);
}
}
const char*
MainWindow::_WindowFrameName() const
{
if (fSinglePackageMode)
return "small window frame";
return "window frame";
}
void
MainWindow::_RestoreWindowFrame(const BMessage& settings)
{
BRect frame = Frame();
BRect windowFrame;
bool fromSettings = false;
if (settings.FindRect(_WindowFrameName(), &windowFrame) == B_OK) {
frame = windowFrame;
fromSettings = true;
} else if (!fSinglePackageMode) {
// Resize to occupy a certain screen size
BRect screenFrame = BScreen(this).Frame();
float width = frame.Width();
float height = frame.Height();
if (width < screenFrame.Width() * .666f
&& height < screenFrame.Height() * .666f) {
frame.bottom = frame.top + screenFrame.Height() * .666f;
frame.right = frame.left
+ std::min(screenFrame.Width() * .666f, height * 7 / 5);
}
}
MoveTo(frame.LeftTop());
ResizeTo(frame.Width(), frame.Height());
if (fromSettings)
MoveOnScreen();
else
CenterOnScreen();
}
void
MainWindow::_InitWorkerThreads()
{
fPendingActionsSem = create_sem(0, "PendingPackageActions");
if (fPendingActionsSem >= 0) {
fPendingActionsWorker = spawn_thread(&_PackageActionWorker,
"Planet Express", B_NORMAL_PRIORITY, this);
if (fPendingActionsWorker >= 0)
resume_thread(fPendingActionsWorker);
} else
fPendingActionsWorker = -1;
fPackageToPopulateSem = create_sem(0, "PopulatePackage");
if (fPackageToPopulateSem >= 0) {
fPopulatePackageWorker = spawn_thread(&_PopulatePackageWorker,
"Package Populator", B_NORMAL_PRIORITY, this);
if (fPopulatePackageWorker >= 0)
resume_thread(fPopulatePackageWorker);
} else
fPopulatePackageWorker = -1;
fNewPackagesToShowSem = create_sem(0, "ShowPackages");
fShowPackagesAcknowledgeSem = create_sem(0, "ShowPackagesAck");
if (fNewPackagesToShowSem >= 0 && fShowPackagesAcknowledgeSem >= 0) {
fShowPackagesWorker = spawn_thread(&_PackagesToShowWorker,
"Good news everyone", B_NORMAL_PRIORITY, this);
if (fShowPackagesWorker >= 0)
resume_thread(fShowPackagesWorker);
} else
fShowPackagesWorker = -1;
}
void
MainWindow::_AdoptModel()
{
{
AutoLocker<BLocker> modelLocker(fModel.Lock());
fVisiblePackages = fModel.CreatePackageList();
AutoLocker<BLocker> listLocker(fPackagesToShowListLock);
fPackagesToShowList = fVisiblePackages;
atomic_add(&fPackagesToShowListID, 1);
}
fFeaturedPackagesView->Clear();
fPackageListView->Clear();
release_sem(fNewPackagesToShowSem);
BAutolock locker(fModel.Lock());
fShowAvailablePackagesItem->SetMarked(fModel.ShowAvailablePackages());
fShowInstalledPackagesItem->SetMarked(fModel.ShowInstalledPackages());
fShowSourcePackagesItem->SetMarked(fModel.ShowSourcePackages());
fShowDevelopPackagesItem->SetMarked(fModel.ShowDevelopPackages());
if (fModel.ShowFeaturedPackages())
fListTabs->Select(0);
else
fListTabs->Select(1);
fFilterView->AdoptModel(fModel);
}
void
MainWindow::_AdoptPackage(const PackageInfoRef& package)
{
{
BAutolock locker(fModel.Lock());
fPackageInfoView->SetPackage(package);
if (fFeaturedPackagesView != NULL)
fFeaturedPackagesView->SelectPackage(package);
if (fPackageListView != NULL)
fPackageListView->SelectPackage(package);
}
_PopulatePackageAsync(false);
}
void
MainWindow::_ClearPackage()
{
fPackageInfoView->Clear();
}
void
MainWindow::_StopBulkLoad()
{
AutoLocker<BLocker> lock(&fBulkLoadProcessCoordinatorLock);
if (fBulkLoadProcessCoordinator != NULL) {
printf("will stop full update process coordinator\n");
fBulkLoadProcessCoordinator->Stop();
}
}
void
MainWindow::_StartBulkLoad(bool force)
{
AutoLocker<BLocker> lock(&fBulkLoadProcessCoordinatorLock);
if (fBulkLoadProcessCoordinator == NULL) {
fBulkLoadProcessCoordinator
= ProcessCoordinatorFactory::CreateBulkLoadCoordinator(
this,
// PackageInfoListener
this,
// ProcessCoordinatorListener
&fModel, force);
fBulkLoadProcessCoordinator->Start();
fRefreshRepositoriesItem->SetEnabled(false);
}
}
/*! This method is called when there is some change in the bulk load process.
A change may mean that a new process has started / stopped etc... or it
may mean that the entire coordinator has finished.
*/
void
MainWindow::CoordinatorChanged(ProcessCoordinatorState& coordinatorState)
{
AutoLocker<BLocker> lock(&fBulkLoadProcessCoordinatorLock);
if (fBulkLoadProcessCoordinator == coordinatorState.Coordinator()) {
if (!coordinatorState.IsRunning())
_BulkLoadProcessCoordinatorFinished(coordinatorState);
else {
_NotifyWorkStatusChange(coordinatorState.Message(),
coordinatorState.Progress());
// show the progress to the user.
}
} else {
if (Logger::IsInfoEnabled()) {
printf("unknown process coordinator changed\n");
}
}
}
void
MainWindow::_BulkLoadProcessCoordinatorFinished(
ProcessCoordinatorState& coordinatorState)
{
if (coordinatorState.ErrorStatus() != B_OK) {
AppUtils::NotifySimpleError(
B_TRANSLATE("Package Update Error"),
B_TRANSLATE("While updating package data, a problem has arisen "
"that may cause data to be outdated or missing from the "
"application's display. Additional details regarding this "
"problem may be able to be obtained from the application "
"logs."));
}
BMessenger messenger(this);
messenger.SendMessage(MSG_BULK_LOAD_DONE);
// it is safe to delete the coordinator here because it is already known
// that all of the processes have completed and their threads will have
// exited safely by this point.
delete fBulkLoadProcessCoordinator;
fBulkLoadProcessCoordinator = NULL;
fRefreshRepositoriesItem->SetEnabled(true);
}
void
MainWindow::_BulkLoadCompleteReceived()
{
_AdoptModel();
_UpdateAvailableRepositories();
fWorkStatusView->SetIdle();
}
/*! Sends off a message to the Window so that it can change the status view
on the front-end in the UI thread.
*/
void
MainWindow::_NotifyWorkStatusChange(const BString& text, float progress)
{
BMessage message(MSG_WORK_STATUS_CHANGE);
if (!text.IsEmpty())
message.AddString(KEY_WORK_STATUS_TEXT, text);
message.AddFloat(KEY_WORK_STATUS_PROGRESS, progress);
this->PostMessage(&message, this);
}
void
MainWindow::_HandleWorkStatusChangeMessageReceived(const BMessage* message)
{
BString text;
float progress;
if (message->FindString(KEY_WORK_STATUS_TEXT, &text) == B_OK)
fWorkStatusView->SetText(text);
if (message->FindFloat(KEY_WORK_STATUS_PROGRESS, &progress) == B_OK)
fWorkStatusView->SetProgress(progress);
}
status_t
MainWindow::_PackageActionWorker(void* arg)
{
MainWindow* window = reinterpret_cast<MainWindow*>(arg);
while (acquire_sem(window->fPendingActionsSem) == B_OK) {
PackageActionRef ref;
{
AutoLocker<BLocker> lock(&window->fPendingActionsLock);
ref = window->fPendingActions.ItemAt(0);
if (ref.Get() == NULL)
break;
window->fPendingActions.Remove(0);
}
BMessenger messenger(window);
BMessage busyMessage(MSG_PACKAGE_WORKER_BUSY);
BString text(ref->Label());
text << B_UTF8_ELLIPSIS;
busyMessage.AddString("reason", text);
messenger.SendMessage(&busyMessage);
ref->Perform();
messenger.SendMessage(MSG_PACKAGE_WORKER_IDLE);
}
return 0;
}
/*! This method will cause the package to have its data refreshed from
the server application. The refresh happens in the background; this method
is asynchronous.
*/
void
MainWindow::_PopulatePackageAsync(bool forcePopulate)
{
// Trigger asynchronous package population from the web-app
{
AutoLocker<BLocker> lock(&fPackageToPopulateLock);
fPackageToPopulate = fPackageInfoView->Package();
fForcePopulatePackage = forcePopulate;
}
release_sem_etc(fPackageToPopulateSem, 1, 0);
if (Logger::IsDebugEnabled()) {
printf("pkg [%s] will be updated from the server.\n",
fPackageToPopulate->Name().String());
}
}
/*! This method will run in the background. The thread will block until there
is a package to be updated. When the thread unblocks, it will update the
package with information from the server.
*/
status_t
MainWindow::_PopulatePackageWorker(void* arg)
{
MainWindow* window = reinterpret_cast<MainWindow*>(arg);
while (acquire_sem(window->fPackageToPopulateSem) == B_OK) {
PackageInfoRef package;
bool force;
{
AutoLocker<BLocker> lock(&window->fPackageToPopulateLock);
package = window->fPackageToPopulate;
force = window->fForcePopulatePackage;
}
if (package.Get() != NULL) {
uint32 populateFlags = Model::POPULATE_USER_RATINGS
| Model::POPULATE_SCREEN_SHOTS
| Model::POPULATE_CHANGELOG;
if (force)
populateFlags |= Model::POPULATE_FORCE;
window->fModel.PopulatePackage(package, populateFlags);
if (Logger::IsDebugEnabled()) {
printf("populating package [%s]\n",
package->Name().String());
}
}
}
return 0;
}
/* static */ status_t
MainWindow::_PackagesToShowWorker(void* arg)
{
MainWindow* window = reinterpret_cast<MainWindow*>(arg);
while (acquire_sem(window->fNewPackagesToShowSem) == B_OK) {
PackageList packageList;
int32 listID = 0;
{
AutoLocker<BLocker> lock(&window->fPackagesToShowListLock);
packageList = window->fPackagesToShowList;
listID = atomic_get(&window->fPackagesToShowListID);
window->fPackagesToShowList.Clear();
}
// Add packages to list views in batches of kPackagesPerUpdate so we
// don't block the window thread for long with each iteration
enum {
kPackagesPerUpdate = 20
};
uint32 packagesInMessage = 0;
BMessage message(MSG_ADD_VISIBLE_PACKAGES);
BMessenger messenger(window);
bool listIsOutdated = false;
for (int i = 0; i < packageList.CountItems(); i++) {
const PackageInfoRef& package = packageList.ItemAtFast(i);
if (packagesInMessage >= kPackagesPerUpdate) {
if (listID != atomic_get(&window->fPackagesToShowListID)) {
// The model was changed again in the meantime, and thus
// our package list isn't current anymore. Send no further
// messags based on this list and go back to start.
listIsOutdated = true;
break;
}
message.AddInt32("list_id", listID);
messenger.SendMessage(&message);
message.MakeEmpty();
packagesInMessage = 0;
// Don't spam the window's message queue, which would make it
// unresponsive (i.e. allows UI messages to get in between our
// messages). When it has processed the message we just sent,
// it will let us know by releasing the semaphore.
acquire_sem(window->fShowPackagesAcknowledgeSem);
}
package->AcquireReference();
message.AddPointer("package_ref", package.Get());
packagesInMessage++;
}
if (listIsOutdated)
continue;
// Send remaining package infos, if any, which didn't make it into
// the last message (count < kPackagesPerUpdate)
if (packagesInMessage > 0) {
message.AddInt32("list_id", listID);
messenger.SendMessage(&message);
acquire_sem(window->fShowPackagesAcknowledgeSem);
}
// Update selected package in list views
messenger.SendMessage(MSG_UPDATE_SELECTED_PACKAGE);
}
return 0;
}
void
MainWindow::_OpenLoginWindow(const BMessage& onSuccessMessage)
{
UserLoginWindow* window = new UserLoginWindow(this,
BRect(0, 0, 500, 400), fModel);
if (onSuccessMessage.what != 0)
window->SetOnSuccessMessage(BMessenger(this), onSuccessMessage);
window->Show();
}
void
MainWindow::_UpdateAuthorization()
{
BString username(fModel.Username());
bool hasUser = !username.IsEmpty();
if (fLogOutItem != NULL)
fLogOutItem->SetEnabled(hasUser);
if (fLogInItem != NULL) {
if (hasUser)
fLogInItem->SetLabel(B_TRANSLATE("Switch account" B_UTF8_ELLIPSIS));
else
fLogInItem->SetLabel(B_TRANSLATE("Log in" B_UTF8_ELLIPSIS));
}
if (fUserMenu != NULL) {
BString label;
if (username.Length() == 0) {
label = B_TRANSLATE("Not logged in");
} else {
label = B_TRANSLATE("Logged in as %User%");
label.ReplaceAll("%User%", username);
}
fUserMenu->Superitem()->SetLabel(label);
}
}
void
MainWindow::_UpdateAvailableRepositories()
{
fRepositoryMenu->RemoveItems(0, fRepositoryMenu->CountItems(), true);
fRepositoryMenu->AddItem(new BMenuItem(B_TRANSLATE("All repositories"),
new BMessage(MSG_DEPOT_SELECTED)));
fRepositoryMenu->AddItem(new BSeparatorItem());
bool foundSelectedDepot = false;
const DepotList& depots = fModel.Depots();
for (int i = 0; i < depots.CountItems(); i++) {
const DepotInfo& depot = depots.ItemAtFast(i);
if (depot.Name().Length() != 0) {
BMessage* message = new BMessage(MSG_DEPOT_SELECTED);
message->AddString("name", depot.Name());
BMenuItem* item = new BMenuItem(depot.Name(), message);
fRepositoryMenu->AddItem(item);
if (depot.Name() == fModel.Depot()) {
item->SetMarked(true);
foundSelectedDepot = true;
}
}
}
if (!foundSelectedDepot)
fRepositoryMenu->ItemAt(0)->SetMarked(true);
}
bool
MainWindow::_SelectedPackageHasWebAppRepositoryCode()
{
const PackageInfoRef& package = fPackageInfoView->Package();
const BString depotName = package->DepotName();
if (depotName.IsEmpty()) {
if (Logger::IsDebugEnabled()) {
printf("the package [%s] has no depot name\n",
package->Name().String());
}
} else {
const DepotInfo* depot = fModel.DepotForName(depotName);
if (depot == NULL) {
printf("the depot [%s] was not able to be found\n",
depotName.String());
} else {
BString repositoryCode = depot->WebAppRepositoryCode();
if (repositoryCode.IsEmpty()) {
printf("the depot [%s] has no web app repository code\n",
depotName.String());
} else {
return true;
}
}
}
return false;
}
void
MainWindow::_RatePackage()
{
if (!_SelectedPackageHasWebAppRepositoryCode()) {
BAlert* alert = new(std::nothrow) BAlert(
B_TRANSLATE("Rating not possible"),
B_TRANSLATE("This package doesn't seem to be on the HaikuDepot "
"Server, so it's not possible to create a new rating "
"or edit an existing rating."),
B_TRANSLATE("OK"));
alert->Go();
return;
}
if (fModel.Username().IsEmpty()) {
BAlert* alert = new(std::nothrow) BAlert(
B_TRANSLATE("Not logged in"),
B_TRANSLATE("You need to be logged into an account before you "
"can rate packages."),
B_TRANSLATE("Cancel"),
B_TRANSLATE("Login or Create account"));
if (alert == NULL)
return;
int32 choice = alert->Go();
if (choice == 1)
_OpenLoginWindow(BMessage(MSG_RATE_PACKAGE));
return;
}
// TODO: Allow only one RatePackageWindow
// TODO: Mechanism for remembering the window frame
RatePackageWindow* window = new RatePackageWindow(this,
BRect(0, 0, 500, 400), fModel);
window->SetPackage(fPackageInfoView->Package());
window->Show();
}
void
MainWindow::_ShowScreenshot()
{
// TODO: Mechanism for remembering the window frame
if (fScreenshotWindow == NULL)
fScreenshotWindow = new ScreenshotWindow(this, BRect(0, 0, 500, 400));
if (fScreenshotWindow->LockWithTimeout(1000) != B_OK)
return;
fScreenshotWindow->SetPackage(fPackageInfoView->Package());
if (fScreenshotWindow->IsHidden())
fScreenshotWindow->Show();
else
fScreenshotWindow->Activate();
fScreenshotWindow->Unlock();
}
↑ V773 The function was exited without releasing the 'alert' pointer. A memory leak is possible.
↑ V773 The function was exited without releasing the 'alert' pointer. A memory leak is possible.