HaikuDepot: data update actions via msgs

This change will stop the main window from
loading all of a selected package's data
even if it may not be viewed; for example,
people don't always view the changelog. This
saves on calls to the server which are not
necessary.

Where data is loaded by the view, the view
layer no longer directly enqueues background
processes, but requests the main window to do
this for it. This unifies the approach for
the view to perform "actions" into a single
approach rather than different approaches in
different circumstances.

The changes also include better tab-
management for the "view package" detail.

Change-Id: I6a08ce9e37d1968655d14b8cc25519d534eccae1
Reviewed-on: https://review.haiku-os.org/c/haiku/+/10845
Tested-by: Commit checker robot <[email protected]>
Reviewed-by: Jérôme Duval <[email protected]>
This commit is contained in:
Andrew Lindesay
2026-04-28 11:54:37 +00:00
parent a0452d3ac1
commit 8fe730d479
21 changed files with 511 additions and 239 deletions
+3 -1
View File
@@ -15,7 +15,6 @@ enum {
MSG_CLIENT_TOO_OLD = 'oldc',
MSG_NETWORK_TRANSPORT_ERROR = 'nett',
MSG_SERVER_ERROR = 'svre',
MSG_SERVER_DATA_CHANGED = 'svdc',
MSG_ALERT_SIMPLE_ERROR = 'nser',
MSG_DID_ADD_USER_RATING = 'adur',
MSG_DID_UPDATE_USER_RATING = 'upur',
@@ -29,6 +28,9 @@ enum {
MSG_PKG_INSTALL = 'pkgi',
MSG_PKG_UNINSTALL = 'pkgu',
MSG_PKG_OPEN = 'pkgo',
MSG_PKG_CACHE_SCREENSHOT = 'pcsc',
MSG_PKG_POPULATE_CHANGELOG = 'pchl',
MSG_PKG_POPULATE_USER_RATINGS = 'purg',
MSG_SHOW_ALL_PACKAGES_TAB = 'sapt'
};
+4
View File
@@ -530,6 +530,10 @@ bool
Model::CanPopulatePackage(const PackageInfoRef& package)
{
const BString depotName = PackageUtils::DepotName(package);
if (depotName == SINGLE_PACKAGE_DEPOT_NAME)
return false;
const DepotInfoRef& depot = DepotForName(depotName);
if (!depot.IsSet())
@@ -22,6 +22,15 @@ ScreenshotCoordinate::ScreenshotCoordinate()
}
ScreenshotCoordinate::ScreenshotCoordinate(const ScreenshotCoordinate& other)
:
fCode(other.fCode),
fWidth(other.fWidth),
fHeight(other.fHeight)
{
}
ScreenshotCoordinate::ScreenshotCoordinate(const BMessage* from)
{
if (from->FindString(kKeyCode, &fCode) != B_OK)
@@ -75,6 +84,16 @@ ScreenshotCoordinate::IsValid() const
}
ScreenshotCoordinate&
ScreenshotCoordinate::operator=(const ScreenshotCoordinate& other)
{
fCode = other.fCode;
fHeight = other.fHeight;
fWidth = other.fWidth;
return *this;
}
bool
ScreenshotCoordinate::operator==(const ScreenshotCoordinate& other) const
{
@@ -82,6 +101,13 @@ ScreenshotCoordinate::operator==(const ScreenshotCoordinate& other) const
}
bool
ScreenshotCoordinate::operator!=(const ScreenshotCoordinate& other) const
{
return !(*this == other);
}
const BString
ScreenshotCoordinate::Key() const
{
@@ -19,6 +19,7 @@ class ScreenshotCoordinate : public BArchivable {
public:
ScreenshotCoordinate(const BMessage* from);
ScreenshotCoordinate(BString code, uint32 width, uint32 height);
ScreenshotCoordinate(const ScreenshotCoordinate& other);
ScreenshotCoordinate();
virtual ~ScreenshotCoordinate();
@@ -26,7 +27,10 @@ public:
uint32 Width() const;
uint32 Height() const;
ScreenshotCoordinate&
operator=(const ScreenshotCoordinate& other);
bool operator==(const ScreenshotCoordinate& other) const;
bool operator!=(const ScreenshotCoordinate& other) const;
bool IsValid() const;
const BString Key() const;
@@ -16,6 +16,10 @@
static const char* const kKeyTitle = "title";
static const char* const kKeyDeskbarLink = "deskbar_link";
static const char* const kKeyScreenshotCoordinates = "screenshot_coordinates";
// #pragma mark - PackageAction
/*! An abstract superclass of the various sorts of package actions which can be
@@ -84,6 +88,9 @@ PackageAction::Archive(BMessage* into, bool deep) const
}
// #pragma mark - UninstallPackageAction
UninstallPackageAction::UninstallPackageAction(const BString& packageName,
const BString& packageTitle)
:
@@ -113,6 +120,9 @@ UninstallPackageAction::MessageWhat() const
}
// #pragma mark - InstallPackageAction
InstallPackageAction::InstallPackageAction(const BString& packageName,
const BString& packageTitle)
:
@@ -142,6 +152,9 @@ InstallPackageAction::MessageWhat() const
}
// #pragma mark - OpenPackageAction
OpenPackageAction::OpenPackageAction(const BString& packageName, const DeskbarLink& deskbarLink)
:
PackageAction("Open", packageName),
@@ -197,3 +210,120 @@ OpenPackageAction::Archive(BMessage* into, bool deep) const
return result;
}
// #pragma mark - CacheScreenshotPackageAction
CacheScreenshotPackageAction::CacheScreenshotPackageAction(const BString& packageName,
const ScreenshotCoordinate& screenshotCoordinate)
:
PackageAction("Screenshot", packageName),
fScreenshotCoordinate(screenshotCoordinate)
{
}
CacheScreenshotPackageAction::CacheScreenshotPackageAction(const BMessage* from)
:
PackageAction(from)
{
BMessage screenshotCoordinateMessage;
if (from->FindMessage(kKeyScreenshotCoordinates, &screenshotCoordinateMessage) == B_OK)
fScreenshotCoordinate = ScreenshotCoordinate(&screenshotCoordinateMessage);
else
HDFATAL("missing key [%s]", kKeyScreenshotCoordinates);
}
CacheScreenshotPackageAction::~CacheScreenshotPackageAction()
{
}
const uint32
CacheScreenshotPackageAction::MessageWhat() const
{
return MSG_PKG_CACHE_SCREENSHOT;
}
ScreenshotCoordinate
CacheScreenshotPackageAction::Coordinate() const
{
return fScreenshotCoordinate;
}
status_t
CacheScreenshotPackageAction::Archive(BMessage* into, bool deep) const
{
status_t result = PackageAction::Archive(into, deep);
if (result == B_OK) {
BMessage screenshotCoordinateMessage;
result = fScreenshotCoordinate.Archive(&screenshotCoordinateMessage);
if (result == B_OK)
result = into->AddMessage(kKeyScreenshotCoordinates, &screenshotCoordinateMessage);
}
return result;
}
// #pragma mark - PopulateChangelogPackageAction
PopulateChangelogPackageAction::PopulateChangelogPackageAction(const BString& packageName)
:
PackageAction("Populate Changelog", packageName)
{
}
PopulateChangelogPackageAction::PopulateChangelogPackageAction(const BMessage* from)
:
PackageAction(from)
{
}
PopulateChangelogPackageAction::~PopulateChangelogPackageAction()
{
}
const uint32
PopulateChangelogPackageAction::MessageWhat() const
{
return MSG_PKG_POPULATE_CHANGELOG;
}
// #pragma mark - PopulateUserRatingsPackageAction
PopulateUserRatingsPackageAction::PopulateUserRatingsPackageAction(const BString& packageName)
:
PackageAction("Populate User Ratings", packageName)
{
}
PopulateUserRatingsPackageAction::PopulateUserRatingsPackageAction(const BMessage* from)
:
PackageAction(from)
{
}
PopulateUserRatingsPackageAction::~PopulateUserRatingsPackageAction()
{
}
const uint32
PopulateUserRatingsPackageAction::MessageWhat() const
{
return MSG_PKG_POPULATE_USER_RATINGS;
}
@@ -10,6 +10,7 @@
#include <String.h>
#include "DeskbarLink.h"
#include "ScreenshotCoordinate.h"
class PackageAction : public BReferenceable, public BArchivable
@@ -80,4 +81,44 @@ private:
};
class CacheScreenshotPackageAction : public PackageAction {
public:
CacheScreenshotPackageAction(const BString& packageName,
const ScreenshotCoordinate& screenshotCoordinate);
CacheScreenshotPackageAction(const BMessage* from);
~CacheScreenshotPackageAction();
virtual const uint32 MessageWhat() const;
ScreenshotCoordinate
Coordinate() const;
virtual status_t Archive(BMessage* into, bool deep = true) const;
private:
ScreenshotCoordinate
fScreenshotCoordinate;
};
class PopulateChangelogPackageAction : public PackageAction {
public:
PopulateChangelogPackageAction(const BString& packageName);
PopulateChangelogPackageAction(const BMessage* from);
virtual ~PopulateChangelogPackageAction();
virtual const uint32 MessageWhat() const;
};
class PopulateUserRatingsPackageAction : public PackageAction {
public:
PopulateUserRatingsPackageAction(const BString& packageName);
PopulateUserRatingsPackageAction(const BMessage* from);
virtual ~PopulateUserRatingsPackageAction();
virtual const uint32 MessageWhat() const;
};
#endif // PACKAGE_ACTION_H
@@ -67,16 +67,6 @@ public:
};
/*! Classes implementing this 'interface' are able to consume process
coordinators. This may be in order to run them.
*/
class ProcessCoordinatorConsumer {
public:
virtual void Consume(ProcessCoordinator *item) = 0;
};
/*! It is possible to create a number of ProcessNodes (themselves associated
with AbstractProcess-s) that may have dependencies (predecessors and
successors) and then an instance of this class is able to coordinate the
@@ -136,28 +136,11 @@ ProcessCoordinatorFactory::CreateBulkLoadCoordinator(Model* model, bool forceLoc
}
/*static*/ ProcessCoordinator*
ProcessCoordinatorFactory::CacheScreenshotCoordinator(Model* model,
ScreenshotCoordinate& screenshotCoordinate)
{
return _CreateSingleProcessCoordinator("CacheScreenshot",
new CacheScreenshotProcess(model, screenshotCoordinate));
}
/*static*/ ProcessCoordinator*
ProcessCoordinatorFactory::PopulatePkgChangelogCoordinator(Model* model, const BString& packageName)
{
return _CreateSingleProcessCoordinator("PopulatePkgChangelog",
new PopulatePkgChangelogFromServerProcess(packageName, model));
}
/*static*/ ProcessCoordinator*
ProcessCoordinatorFactory::PopulatePkgUserRatingsCoordinator(Model* model,
const BString& packageName)
{
return _CreateSingleProcessCoordinator("PopulatePkgUserRatings",
return _CreateSingleProcessCoordinator("PopulatePkgUserRatings", NULL,
new PopulatePkgUserRatingsFromServerProcess(packageName, model));
}
@@ -166,12 +149,8 @@ ProcessCoordinatorFactory::PopulatePkgUserRatingsCoordinator(Model* model,
ProcessCoordinatorFactory::CreateInstallPackageActionCoordinator(Model* model,
const InstallPackageAction& action)
{
ProcessCoordinator* processCoordinator
= new ProcessCoordinator("InstallPackage", new BMessage(MSG_PACKAGE_ACTION_DONE));
AbstractProcessNode* processNode
= new ThreadedProcessNode(new InstallPackageProcess(action.PackageName(), model), 10);
processCoordinator->AddNode(processNode);
return processCoordinator;
return _CreateSingleProcessCoordinator("InstallPackage", new BMessage(MSG_PACKAGE_ACTION_DONE),
new InstallPackageProcess(action.PackageName(), model));
}
@@ -179,12 +158,9 @@ ProcessCoordinatorFactory::CreateInstallPackageActionCoordinator(Model* model,
ProcessCoordinatorFactory::CreateUninstallPackageActionCoordinator(Model* model,
const UninstallPackageAction& action)
{
ProcessCoordinator* processCoordinator
= new ProcessCoordinator("UninstallPackage", new BMessage(MSG_PACKAGE_ACTION_DONE));
AbstractProcessNode* processNode
= new ThreadedProcessNode(new UninstallPackageProcess(action.PackageName(), model), 10);
processCoordinator->AddNode(processNode);
return processCoordinator;
return _CreateSingleProcessCoordinator("UninstallPackage",
new BMessage(MSG_PACKAGE_ACTION_DONE),
new UninstallPackageProcess(action.PackageName(), model));
}
@@ -192,12 +168,37 @@ ProcessCoordinatorFactory::CreateUninstallPackageActionCoordinator(Model* model,
ProcessCoordinatorFactory::CreateOpenPackageActionCoordinator(Model* model,
const OpenPackageAction& action)
{
ProcessCoordinator* processCoordinator
= new ProcessCoordinator("OpenPackage", new BMessage(MSG_PACKAGE_ACTION_DONE));
AbstractProcessNode* processNode = new ThreadedProcessNode(
return _CreateSingleProcessCoordinator("OpenPackage", new BMessage(MSG_PACKAGE_ACTION_DONE),
new OpenPackageProcess(action.PackageName(), model, action.Link()));
processCoordinator->AddNode(processNode);
return processCoordinator;
}
/*static*/ ProcessCoordinator*
ProcessCoordinatorFactory::CreateCacheScreenshotPackageActionCoordinator(Model* model,
const CacheScreenshotPackageAction& action)
{
return _CreateSingleProcessCoordinator("CacheScreenshot", new BMessage(MSG_PACKAGE_ACTION_DONE),
new CacheScreenshotProcess(model, action.Coordinate()));
}
/*static*/ ProcessCoordinator*
ProcessCoordinatorFactory::CreatePopulateChangelogPackageActionCoordinator(Model* model,
const PopulateChangelogPackageAction& action)
{
return _CreateSingleProcessCoordinator("PopulatePkgChangelog",
new BMessage(MSG_PACKAGE_ACTION_DONE),
new PopulatePkgChangelogFromServerProcess(action.PackageName(), model));
}
/*static*/ ProcessCoordinator*
ProcessCoordinatorFactory::CreatePopulateUserRatingsPackageActionCoordinator(Model* model,
const PopulateUserRatingsPackageAction& action)
{
return _CreateSingleProcessCoordinator("PopulatePkgUserRatings",
new BMessage(MSG_PACKAGE_ACTION_DONE),
new PopulatePkgUserRatingsFromServerProcess(action.PackageName(), model));
}
@@ -225,10 +226,10 @@ ProcessCoordinatorFactory::_CalculateServerProcessOptions()
/*static*/ ProcessCoordinator*
ProcessCoordinatorFactory::_CreateSingleProcessCoordinator(const char* name,
ProcessCoordinatorFactory::_CreateSingleProcessCoordinator(const char* name, BMessage* message,
AbstractProcess* process)
{
ProcessCoordinator* processCoordinator = new ProcessCoordinator(name);
ProcessCoordinator* processCoordinator = new ProcessCoordinator(name, message);
AbstractProcessNode* cacheScreenshotNode = new ThreadedProcessNode(process);
processCoordinator->AddNode(cacheScreenshotNode);
return processCoordinator;
@@ -33,12 +33,6 @@ public:
UserDetailVerifierListener* userDetailVerifierListener,
Model* model);
static ProcessCoordinator* CacheScreenshotCoordinator(
Model* model, ScreenshotCoordinate& screenshotCoordinate);
static ProcessCoordinator* PopulatePkgChangelogCoordinator(Model* model,
const BString& packageName);
static ProcessCoordinator* PopulatePkgUserRatingsCoordinator(Model* model,
const BString& packageName);
@@ -51,12 +45,20 @@ public:
static ProcessCoordinator* CreateOpenPackageActionCoordinator(Model* model,
const OpenPackageAction& action);
static ProcessCoordinator* CreateCacheScreenshotPackageActionCoordinator(Model* model,
const CacheScreenshotPackageAction& action);
static ProcessCoordinator* CreatePopulateChangelogPackageActionCoordinator(Model* model,
const PopulateChangelogPackageAction& action);
static ProcessCoordinator* CreatePopulateUserRatingsPackageActionCoordinator(Model* model,
const PopulateUserRatingsPackageAction& action);
private:
static uint32 _CalculateServerProcessOptions();
static ProcessCoordinator* _CreateSingleProcessCoordinator(const char* name,
static ProcessCoordinator* _CreateSingleProcessCoordinator(const char* name, BMessage* message,
AbstractProcess *process);
};
#endif // PROCESS_COORDINATOR_FACTORY_H
@@ -1,5 +1,5 @@
/*
* Copyright 2023-2025, Andrew Lindesay <[email protected]>.
* Copyright 2023-2026, Andrew Lindesay <[email protected]>.
* All rights reserved. Distributed under the terms of the MIT License.
*/
#include "CacheScreenshotProcess.h"
@@ -15,8 +15,8 @@
#define B_TRANSLATION_CONTEXT "CacheScreenshotProcess"
CacheScreenshotProcess::CacheScreenshotProcess(Model *model,
ScreenshotCoordinate& screenshotCoordinate)
CacheScreenshotProcess::CacheScreenshotProcess(Model* model,
const ScreenshotCoordinate& screenshotCoordinate)
:
fModel(model),
fScreenshotCoordinate(screenshotCoordinate)
@@ -1,5 +1,5 @@
/*
* Copyright 2023, Andrew Lindesay <[email protected]>.
* Copyright 2023-2026, Andrew Lindesay <[email protected]>.
* All rights reserved. Distributed under the terms of the MIT License.
*/
#ifndef CACHE_SCREENSHOT_PROCESS__H
@@ -15,8 +15,8 @@ class Model;
class CacheScreenshotProcess : public AbstractProcess {
public:
CacheScreenshotProcess(
Model* model, ScreenshotCoordinate& screenshotCoordinate);
CacheScreenshotProcess(Model* model,
const ScreenshotCoordinate& screenshotCoordinate);
virtual ~CacheScreenshotProcess();
const char* Name() const;
+5 -1
View File
@@ -137,7 +137,11 @@ App::MessageReceived(BMessage* message)
_AlertSimpleError(message);
break;
case MSG_SERVER_DATA_CHANGED:
case MSG_PKG_POPULATE_USER_RATINGS:
// This will come from the `RatePackageWindow` when somebody has
// modified a user rating in the UI. The main window and trigger
// a background process to re-load the user rating data and then
// show it.
fMainWindow->PostMessage(message);
break;
+32 -84
View File
@@ -236,7 +236,7 @@ MainWindow::MainWindow(const BMessage& settings)
fFilterView = new FilterView();
fFeaturedPackagesView = new FeaturedPackagesView(fModel);
fPackageListView = new PackageListView(&fModel);
fPackageInfoView = new PackageInfoView(&fModel, this);
fPackageInfoView = new PackageInfoView(&fModel);
fSplitView = new BSplitView(B_VERTICAL, 5.0f);
@@ -329,7 +329,7 @@ MainWindow::MainWindow(const BMessage& settings, const PackageInfoRef package)
fPackageInfoListener = PackageInfoListenerRef(new MainWindowPackageInfoListener(this), true);
fFilterView = new FilterView();
fPackageInfoView = new PackageInfoView(&fModel, this);
fPackageInfoView = new PackageInfoView(&fModel);
fWorkStatusView = new WorkStatusView("work status");
BLayoutBuilder::Group<>(this, B_VERTICAL)
@@ -580,23 +580,6 @@ MainWindow::MessageReceived(BMessage* message)
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(shared_message_keys::kKeyPackageName, &name) == B_OK) {
if (fPackageInfoView->Package()->Name() == name) {
_PopulatePackageAsync(true);
} else {
HDDEBUG("pkg [%s] is updated on the server, but is not selected so will not be "
"updated.",
name.String());
}
}
break;
}
case MSG_INCREMENT_VIEW_COUNTER:
_HandleIncrementViewCounter(message);
break;
@@ -688,6 +671,36 @@ MainWindow::MessageReceived(BMessage* message)
break;
}
case MSG_PKG_CACHE_SCREENSHOT:
{
CacheScreenshotPackageAction action(message);
ProcessCoordinator* coordinator
= ProcessCoordinatorFactory::CreateCacheScreenshotPackageActionCoordinator(&fModel,
action);
_AddProcessCoordinator(coordinator);
break;
}
case MSG_PKG_POPULATE_CHANGELOG:
{
PopulateChangelogPackageAction action(message);
ProcessCoordinator* coordinator
= ProcessCoordinatorFactory::CreatePopulateChangelogPackageActionCoordinator(
&fModel, action);
_AddProcessCoordinator(coordinator);
break;
}
case MSG_PKG_POPULATE_USER_RATINGS:
{
PopulateUserRatingsPackageAction action(message);
ProcessCoordinator* coordinator
= ProcessCoordinatorFactory::CreatePopulateUserRatingsPackageActionCoordinator(
&fModel, action);
_AddProcessCoordinator(coordinator);
break;
}
case MSG_PKG_OPEN:
{
OpenPackageAction action(message);
@@ -795,13 +808,6 @@ MainWindow::StoreSettings(BMessage& settings)
}
void
MainWindow::Consume(ProcessCoordinator* item)
{
_AddProcessCoordinator(item);
}
/*! This method is invoked in the situation that a package changes in the model.
The events are processed into a `BMessage` which is then posted to the
@@ -1310,8 +1316,6 @@ MainWindow::_AdoptPackage(const PackageInfoRef& package)
if (fPackageListView != NULL)
fPackageListView->SelectPackage(package);
}
_PopulatePackageAsync(false);
}
@@ -1511,62 +1515,6 @@ MainWindow::_HandleWorkStatusChangeMessageReceived(const BMessage* message)
}
/*! Initially only superficial data is loaded from the server into the data
model of the packages. When the package is viewed, additional data needs
to be populated including ratings.
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)
{
const PackageInfoRef package = fPackageInfoView->Package();
if (!fModel.CanPopulatePackage(package))
return;
const char* packageNameStr = package->Name().String();
PackageLocalizedTextRef localized = package->LocalizedText();
bool networkAvailable = ServerHelper::IsNetworkAvailable();
if (localized.IsSet()) {
if (forcePopulate || localized->Changelog().IsEmpty()) {
if (localized->HasChangelog()) {
if (networkAvailable) {
_AddProcessCoordinator(
ProcessCoordinatorFactory::PopulatePkgChangelogCoordinator(&fModel,
package->Name()));
HDINFO("pkg [%s] will have changelog updated from server.", packageNameStr);
} else {
HDINFO(
"pkg [%s] will not have changelog updated from server; network unavailable",
packageNameStr);
}
} else {
HDINFO("pkg [%s] does not have a changelog -- won't try fetch it.", packageNameStr);
}
}
}
if (forcePopulate || RatingUtils::ShouldTryPopulateUserRatings(package->UserRatingInfo())) {
if (networkAvailable) {
_AddProcessCoordinator(ProcessCoordinatorFactory::PopulatePkgUserRatingsCoordinator(
&fModel, package->Name()));
HDINFO("pkg [%s] will have user ratings updated from server.", packageNameStr);
} else {
HDINFO("pkg [%s] won't have user ratings updated from server; network unavailable",
packageNameStr);
}
} else {
HDDEBUG("pkg [%s] not have user ratings updated from server.", packageNameStr);
}
}
void
MainWindow::_OpenSettingsWindow()
{
+3 -8
View File
@@ -46,9 +46,9 @@ extern const char* const kKeyWindowSettings;
}; // namespace main_window_keys
class MainWindow :
private ProcessCoordinatorConsumer, public ProcessCoordinatorListener,
public UserDetailVerifierListener, public BWindow {
class MainWindow : public ProcessCoordinatorListener,
public UserDetailVerifierListener,
public BWindow {
public:
MainWindow(const BMessage& settings);
MainWindow(const BMessage& settings,
@@ -61,9 +61,6 @@ public:
void StoreSettings(BMessage& message);
// ProcessCoordinatorConsumer
virtual void Consume(ProcessCoordinator *item);
// ProcessCoordinatorListener
virtual void CoordinatorChanged(
ProcessCoordinatorState& coordinatorState);
@@ -115,7 +112,6 @@ private:
void _HandleIncrementViewCounter(const BMessage* message);
void _IncrementViewCounter(const BString& packageName);
void _PopulatePackageAsync(bool forcePopulate);
void _StartBulkLoad(bool force = false);
void _BulkLoadCompleteReceived(status_t errorStatus);
@@ -213,5 +209,4 @@ private:
};
#endif // MAIN_WINDOW_H
+135 -47
View File
@@ -45,7 +45,6 @@
#include "PackageInfo.h"
#include "PackageManager.h"
#include "PackageUtils.h"
#include "ProcessCoordinatorFactory.h"
#include "RatingView.h"
#include "ScrollableGroupView.h"
#include "ServerHelper.h"
@@ -1226,35 +1225,84 @@ public:
fAboutView->SetScreenshotThumbnail(bitmap);
}
void SetPackage(const PackageInfoRef package, bool switchToDefaultTab)
void PopulateDataForTab(int32 index)
{
if (switchToDefaultTab)
Select(TAB_ABOUT);
switch (index) {
case TAB_ABOUT:
break;
case TAB_RATINGS:
_MaybePopulateUserRatings(fPackage);
break;
case TAB_CHANGELOG:
_MaybePopulateChangelog(fPackage);
break;
case TAB_CONTENTS:
break;
default:
HDERROR("unhandled tab index for data population");
break;
}
}
void SetPackage(const PackageInfoRef package)
{
fPackage = package;
bool enableUserRatingsTab = false;
bool enableChangelogTab = false;
bool enableContentsTab = false;
if (package.IsSet()) {
PackageLocalizedTextRef localizedText = package->LocalizedText();
if (localizedText.IsSet())
enableChangelogTab = localizedText->HasChangelog();
enableChangelogTab = _ShowChangelog(package);
enableContentsTab = PackageUtils::IsActivatedOrLocalFile(package);
enableUserRatingsTab = _PackageCanHaveRatings(package);
enableUserRatingsTab = _ShowUserRatings(package);
}
TabAt(TAB_CHANGELOG)->SetEnabled(enableChangelogTab);
TabAt(TAB_CONTENTS)->SetEnabled(enableContentsTab);
TabAt(TAB_RATINGS)->SetEnabled(enableUserRatingsTab);
Invalidate(TabFrame(TAB_CHANGELOG));
Invalidate(TabFrame(TAB_CONTENTS));
fAboutView->SetPackage(package);
fUserRatingsView->SetPackage(package);
fChangelogView->SetPackage(package);
fContentsView->SetPackage(package);
int32 currentTab = Selection();
int32 futureTab = currentTab;
switch (futureTab) {
case TAB_ABOUT:
break;
case TAB_RATINGS:
if (!enableUserRatingsTab)
futureTab = TAB_ABOUT;
break;
case TAB_CHANGELOG:
if (!enableChangelogTab)
futureTab = TAB_ABOUT;
break;
case TAB_CONTENTS:
if (!enableContentsTab)
futureTab = TAB_ABOUT;
break;
default:
HDERROR("unhandled tab index");
futureTab = TAB_ABOUT;
break;
}
if (currentTab != futureTab)
Select(futureTab);
else
PopulateDataForTab(currentTab);
}
/*! This is overridden so that data can be loaded as the tab is selected
on-demand rather than load all the data anyway.
*/
virtual void Select(int32 index) {
BTabView::Select(index);
PopulateDataForTab(index);
}
void Clear()
@@ -1266,22 +1314,78 @@ public:
}
private:
/*! It is only possible for a package to have ratings if it is associated with a server-side
repository (depot). Otherwise there will be no means to display ratings.
*/
bool _PackageCanHaveRatings(const PackageInfoRef package)
bool _ShowUserRatings(const PackageInfoRef package)
{
BString depotName = PackageUtils::DepotName(package);
if (!package.IsSet())
return false;
return PackageUtils::IsPopulatedUserRatings(package)
|| (fModel->CanPopulatePackage(package) && ServerHelper::IsNetworkAvailable()
&& PackageUtils::HasUserRatings(package));
}
if (depotName == SINGLE_PACKAGE_DEPOT_NAME)
return false;
bool _ShowChangelog(const PackageInfoRef package) {
if (!package.IsSet())
return false;
return PackageUtils::IsPopulatedChangelog(package)
|| (fModel->CanPopulatePackage(package) && ServerHelper::IsNetworkAvailable()
&& PackageUtils::HasChangelog(package));
}
const DepotInfoRef depotInfo = fModel->DepotForName(depotName);
/*! Returns true if the changelog should be available.
*/
void _MaybePopulateChangelog(const PackageInfoRef package) {
if (!package.IsSet())
return;
if (depotInfo.IsSet())
return !depotInfo->WebAppRepositoryCode().IsEmpty();
if (PackageUtils::IsPopulatedChangelog(package))
return;
return false;
if (!fModel->CanPopulatePackage(package))
return;
if (!ServerHelper::IsNetworkAvailable()) {
HDINFO("will skip populating changelog for [%s] as no network is available",
package->Name().String());
return;
}
if (!PackageUtils::HasChangelog(package))
return;
// This will case a background task to start which will fetch the changelog
// from the server and populate it into the package data stored in the model.
// The correct view will be updated when the data arrives to present.
PopulateChangelogPackageAction action(package->Name());
BMessage message = action.Message();
Window()->PostMessage(&message);
}
void _MaybePopulateUserRatings(const PackageInfoRef package) {
if (!package.IsSet())
return;
if (PackageUtils::IsPopulatedUserRatings(package))
return;
if (!fModel->CanPopulatePackage(package))
return;
if (!ServerHelper::IsNetworkAvailable()) {
HDINFO("will skip populating user ratings for [%s] as no network is available",
package->Name().String());
return;
}
if (!PackageUtils::HasUserRatings(package))
return;
// This will case a background task to start which will fetch the user ratings
// from the server and populate it into the package data stored in the model.
// The correct view will be updated when the data arrives to present.
PopulateUserRatingsPackageAction action(package->Name());
BMessage message = action.Message();
Window()->PostMessage(&message);
}
private:
@@ -1290,18 +1394,17 @@ private:
UserRatingsView* fUserRatingsView;
ChangelogView* fChangelogView;
ContentsView* fContentsView;
PackageInfoRef fPackage;
};
// #pragma mark - PackageInfoView
PackageInfoView::PackageInfoView(Model* model,
ProcessCoordinatorConsumer* processCoordinatorConsumer)
PackageInfoView::PackageInfoView(Model* model)
:
BView("package info view", 0),
fModel(model),
fProcessCoordinatorConsumer(processCoordinatorConsumer)
fModel(model)
{
fCardLayout = new BCardLayout();
SetLayout(fCardLayout);
@@ -1362,23 +1465,9 @@ PackageInfoView::SetPackage(const PackageInfoRef& packageRef)
return;
}
bool switchToDefaultTab = true;
if (fPackage == packageRef) {
// When asked to display the already showing package ref,
// don't switch to the default tab.
switchToDefaultTab = false;
} else if (fPackage.IsSet() && packageRef.IsSet() && fPackage->Name() == packageRef->Name()) {
// When asked to display a different PackageInfo instance,
// but it has the same package title as the already showing
// instance, this probably means there was a repository
// refresh and we are in fact still requested to show the
// same package as before the refresh.
switchToDefaultTab = false;
}
fTitleView->SetPackage(packageRef);
fPackageActionView->SetPackage(packageRef);
fPagesView->SetPackage(packageRef, switchToDefaultTab);
fPagesView->SetPackage(packageRef);
_SetPackageScreenshotThumb(packageRef);
@@ -1411,7 +1500,7 @@ PackageInfoView::_HandlePackageChanged(const PackageInfoChangeEvent& event)
if ((changes & PKG_CHANGED_LOCALIZED_TEXT) != 0 || (changes & PKG_CHANGED_SCREENSHOTS) != 0
|| (changes & PKG_CHANGED_RATINGS) != 0 || (changes & PKG_CHANGED_LOCAL_INFO) != 0) {
fPagesView->SetPackage(package, false);
fPagesView->SetPackage(package);
}
if ((changes & PKG_CHANGED_LOCALIZED_TEXT) != 0 || (changes & PKG_CHANGED_RATINGS) != 0)
@@ -1443,7 +1532,6 @@ PackageInfoView::HandlePackagesChanged(const std::vector<PackageInfoChangeEvent>
the background. A message will come through later once it is
cached and ready to load.
*/
void
PackageInfoView::_SetPackageScreenshotThumb(const PackageInfoRef& package)
{
@@ -1467,9 +1555,9 @@ PackageInfoView::_SetPackageScreenshotThumb(const PackageInfoRef& package)
HDINFO("screenshot won't be cached [%s] -- network unavailable", packageNameCStr);
} else {
HDDEBUG("screenshot is not cached [%s] -- will cache it", packageNameCStr);
ProcessCoordinator* processCoordinator
= ProcessCoordinatorFactory::CacheScreenshotCoordinator(fModel, desiredCoordinate);
fProcessCoordinatorConsumer->Consume(processCoordinator);
CacheScreenshotPackageAction action(package->Name(), desiredCoordinate);
BMessage message = action.Message();
Window()->PostMessage(&message);
}
} else {
HDDEBUG("no screenshot for pkg [%s]", packageNameCStr);
+1 -6
View File
@@ -13,7 +13,6 @@
#include "Model.h"
#include "PackageInfo.h"
#include "PackageInfoListener.h"
#include "ProcessCoordinator.h"
class BCardLayout;
@@ -31,9 +30,7 @@ enum {
class PackageInfoView : public BView {
public:
PackageInfoView(Model* model,
ProcessCoordinatorConsumer*
processCoordinatorConsumer);
PackageInfoView(Model* model);
virtual ~PackageInfoView();
virtual void AttachedToWindow();
@@ -68,8 +65,6 @@ private:
PackageInfoRef fPackage;
OnePackageMessagePackageListener*
fPackageListener;
ProcessCoordinatorConsumer*
fProcessCoordinatorConsumer;
};
#endif // PACKAGE_INFO_VIEW_H
+3 -2
View File
@@ -26,6 +26,7 @@
#include "LanguageMenuUtils.h"
#include "Logger.h"
#include "MarkupParser.h"
#include "PackageAction.h"
#include "PackageUtils.h"
#include "RatingView.h"
#include "ServerHelper.h"
@@ -371,8 +372,8 @@ RatePackageWindow::MessageReceived(BMessage* message)
void
RatePackageWindow::_RefreshPackageData()
{
BMessage message(MSG_SERVER_DATA_CHANGED);
message.AddString(shared_message_keys::kKeyPackageName, fPackage->Name());
PopulateUserRatingsPackageAction action(fPackage->Name());
BMessage message = action.Message();
be_app->PostMessage(&message);
}
+61 -1
View File
@@ -1,5 +1,5 @@
/*
* Copyright 2024-2025, Andrew Lindesay <apl@lindesay.co.nz>.
* Copyright 2024-2026, Andrew Lindesay <apl@lindesay.co.nz>.
* All rights reserved. Distributed under the terms of the MIT License.
*/
@@ -52,6 +52,66 @@ PackageUtils::Summary(const PackageInfoRef& package, BString& summary)
}
/*static*/ bool
PackageUtils::HasChangelog(const PackageInfoRef& package)
{
if (package.IsSet()) {
PackageLocalizedTextRef localizedText = package->LocalizedText();
if (localizedText.IsSet())
return localizedText->HasChangelog();
}
return false;
}
/*static*/ bool
PackageUtils::IsPopulatedChangelog(const PackageInfoRef& package)
{
if (package.IsSet()) {
PackageLocalizedTextRef localizedText = package->LocalizedText();
if (localizedText.IsSet())
return !localizedText->Changelog().IsEmpty();
}
return false;
}
/*static*/ bool
PackageUtils::HasUserRatings(const PackageInfoRef& package)
{
if (package.IsSet()) {
PackageUserRatingInfoRef userRatingInfo = package->UserRatingInfo();
if (userRatingInfo.IsSet()) {
UserRatingSummaryRef summary = userRatingInfo->Summary();
if (summary.IsSet())
return summary->RatingCount() > 0;
}
}
return false;
}
/*static*/ bool
PackageUtils::IsPopulatedUserRatings(const PackageInfoRef& package)
{
if (package.IsSet()) {
PackageUserRatingInfoRef userRatingInfo = package->UserRatingInfo();
if (userRatingInfo.IsSet())
return userRatingInfo->UserRatingsPopulated();
}
return false;
}
/*static*/ const BString
PackageUtils::DepotName(const PackageInfoRef& package)
{
+7 -1
View File
@@ -1,5 +1,5 @@
/*
* Copyright 2024-2025, Andrew Lindesay <apl@lindesay.co.nz>.
* Copyright 2024-2026, Andrew Lindesay <apl@lindesay.co.nz>.
* All rights reserved. Distributed under the terms of the MIT License.
*/
#ifndef PACKAGE_UTILS_H
@@ -15,6 +15,12 @@ public:
static void Title(const PackageInfoRef& package, BString& title);
static void Summary(const PackageInfoRef& package, BString& summary);
static bool HasChangelog(const PackageInfoRef& package);
static bool IsPopulatedChangelog(const PackageInfoRef& package);
static bool HasUserRatings(const PackageInfoRef& package);
static bool IsPopulatedUserRatings(const PackageInfoRef& package);
static PackageVersionRef
Version(const PackageInfoRef& package);
static const BString Architecture(const PackageInfoRef& package);
+1 -24
View File
@@ -1,5 +1,5 @@
/*
* Copyright 2020-2025, Andrew Lindesay <apl@lindesay.co.nz>.
* Copyright 2020-2026, Andrew Lindesay <apl@lindesay.co.nz>.
* All rights reserved. Distributed under the terms of the MIT License.
*/
@@ -99,26 +99,3 @@ RatingUtils::Draw(BView* target, BPoint at, float value, const BBitmap* star)
target->FillRect(shadeOverRect, B_SOLID_HIGH);
}
}
/*! With the `userRatingInfo` provided, does it make sense for the application
to attempt to download the user ratings? If it looks like there are none
then it's making no sense and if it has already downloaded some then it
also does not make any sense.
*/
/*static*/ bool
RatingUtils::ShouldTryPopulateUserRatings(PackageUserRatingInfoRef userRatingInfo)
{
if (!userRatingInfo.IsSet())
return true;
UserRatingSummaryRef summary = userRatingInfo->Summary();
if (!summary.IsSet())
return true;
if (summary->RatingCount() == 0)
return false;
return !userRatingInfo->UserRatingsPopulated();
}
+1 -3
View File
@@ -1,5 +1,5 @@
/*
* Copyright 2020-2025, Andrew Lindesay <apl@lindesay.co.nz>.
* Copyright 2020-2026, Andrew Lindesay <apl@lindesay.co.nz>.
* All rights reserved. Distributed under the terms of the MIT License.
*/
#ifndef RATING_UTILS_H
@@ -36,8 +36,6 @@ public:
static void Draw(BView* target, BPoint at, float value,
const BBitmap* star);
static void Draw(BView* target, BPoint at, float value);
static bool ShouldTryPopulateUserRatings(PackageUserRatingInfoRef userRatingInfo);
};