Add the new Repositories preflet.

Also includes changes to HaikuDepot to change wording and add the menu item
to open the Repositories preflet.

Signed-off-by: Augustin Cavalier <[email protected]>
Closes #13147.
This commit is contained in:
Brian Hill
2017-01-07 13:50:45 -05:00
committed by Augustin Cavalier
parent 564aac4209
commit 5bf2b6eb74
23 changed files with 2382 additions and 7 deletions
Binary file not shown.
+3 -3
View File
@@ -46,8 +46,8 @@ FilterView::FilterView()
fShowField = new BMenuField("category", B_TRANSLATE("Category:"), showMenu);
// Construct repository popup
BPopUpMenu* repositoryMenu = new BPopUpMenu(B_TRANSLATE("Depot"));
fRepositoryField = new BMenuField("repository", B_TRANSLATE("Depot:"),
BPopUpMenu* repositoryMenu = new BPopUpMenu(B_TRANSLATE("Repository"));
fRepositoryField = new BMenuField("repository", B_TRANSLATE("Repository:"),
repositoryMenu);
// Construct search terms field
@@ -125,7 +125,7 @@ FilterView::AdoptModel(const Model& model)
BMenu* repositoryMenu = fRepositoryField->Menu();
repositoryMenu->RemoveItems(0, repositoryMenu->CountItems(), true);
repositoryMenu->AddItem(new BMenuItem(B_TRANSLATE("All depots"),
repositoryMenu->AddItem(new BMenuItem(B_TRANSLATE("All repositories"),
new BMessage(MSG_DEPOT_SELECTED)));
repositoryMenu->AddItem(new BSeparatorItem());
+12 -4
View File
@@ -24,6 +24,7 @@
#include <MenuBar.h>
#include <MenuItem.h>
#include <Messenger.h>
#include <Roster.h>
#include <Screen.h>
#include <ScrollView.h>
#include <StringList.h>
@@ -63,7 +64,8 @@
enum {
MSG_MODEL_WORKER_DONE = 'mmwd',
MSG_REFRESH_DEPOTS = 'mrdp',
MSG_REFRESH_REPOS = 'mrrp',
MSG_MANAGE_REPOS = 'mmrp',
MSG_LOG_IN = 'lgin',
MSG_LOG_OUT = 'lgot',
MSG_AUTHORIZATION_CHANGED = 'athc',
@@ -316,10 +318,14 @@ MainWindow::MessageReceived(BMessage* message)
_StartRefreshWorker(false);
break;
case MSG_REFRESH_DEPOTS:
case MSG_REFRESH_REPOS:
_StartRefreshWorker(true);
break;
case MSG_MANAGE_REPOS:
be_roster->Launch("application/x-vnd.Haiku-Repositories");
break;
case MSG_LOG_IN:
_OpenLoginWindow(BMessage());
break;
@@ -567,8 +573,10 @@ void
MainWindow::_BuildMenu(BMenuBar* menuBar)
{
BMenu* menu = new BMenu(B_TRANSLATE("Tools"));
menu->AddItem(new BMenuItem(B_TRANSLATE("Refresh depots"),
new BMessage(MSG_REFRESH_DEPOTS)));
menu->AddItem(new BMenuItem(B_TRANSLATE("Refresh repositories"),
new BMessage(MSG_REFRESH_REPOS)));
menu->AddItem(new BMenuItem(B_TRANSLATE("Manage repositories"),
new BMessage(MSG_MANAGE_REPOS)));
menuBar->AddItem(menu);
+1
View File
@@ -17,6 +17,7 @@ SubInclude HAIKU_TOP src preferences mouse ;
SubInclude HAIKU_TOP src preferences network ;
SubInclude HAIKU_TOP src preferences notifications ;
SubInclude HAIKU_TOP src preferences printers ;
SubInclude HAIKU_TOP src preferences repositories ;
SubInclude HAIKU_TOP src preferences screen ;
SubInclude HAIKU_TOP src preferences screensaver ;
SubInclude HAIKU_TOP src preferences shortcuts ;
@@ -0,0 +1,136 @@
/*
* Copyright 2017 Haiku Inc. All rights reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
* Brian Hill
*/
#include "AddRepoWindow.h"
#include <Alert.h>
#include <Application.h>
#include <Catalog.h>
#include <Clipboard.h>
#include <LayoutBuilder.h>
#include "constants.h"
#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "AddRepoWindow"
static float sAddWindowWidth = 500.0;
AddRepoWindow::AddRepoWindow(BRect size, const BMessenger& messenger)
:
BWindow(BRect(0, 0, sAddWindowWidth, 10), "AddWindow", B_MODAL_WINDOW,
B_ASYNCHRONOUS_CONTROLS | B_AUTO_UPDATE_SIZE_LIMITS | B_CLOSE_ON_ESCAPE),
fReplyMessenger(messenger)
{
fText = new BTextControl("text", B_TRANSLATE_COMMENT("Repository URL:",
"Text box label"), "", new BMessage(ADD_BUTTON_PRESSED));
fAddButton = new BButton(B_TRANSLATE_COMMENT("Add", "Button label"),
new BMessage(ADD_BUTTON_PRESSED));
fAddButton->MakeDefault(true);
fCancelButton = new BButton(kCancelLabel,
new BMessage(CANCEL_BUTTON_PRESSED));
BLayoutBuilder::Group<>(this, B_VERTICAL)
.SetInsets(B_USE_WINDOW_SPACING)
.Add(fText)
.AddGroup(B_HORIZONTAL, B_USE_DEFAULT_SPACING)
.AddGlue()
.Add(fCancelButton)
.Add(fAddButton)
.End()
.End();
_GetClipboardData();
fText->MakeFocus();
// Move to the center of the preflet window
CenterIn(size);
float widthDifference = size.Width() - Frame().Width();
if (widthDifference < 0)
MoveBy(widthDifference / 2.0, 0);
Show();
}
void
AddRepoWindow::Quit()
{
fReplyMessenger.SendMessage(ADD_WINDOW_CLOSED);
BWindow::Quit();
}
void
AddRepoWindow::MessageReceived(BMessage* message)
{
switch (message->what)
{
case CANCEL_BUTTON_PRESSED:
if (QuitRequested())
Quit();
break;
case ADD_BUTTON_PRESSED: {
BString url(fText->Text());
if (url != "") {
// URL must have a protocol
if (url.FindFirst("://") == B_ERROR) {
BAlert* alert = new BAlert("error",
B_TRANSLATE_COMMENT("The URL must start with a "
"protocol, for example http:// or https://",
"Add URL error message"),
kOKLabel, NULL, NULL, B_WIDTH_AS_USUAL, B_STOP_ALERT);
alert->SetFeel(B_MODAL_APP_WINDOW_FEEL);
alert->Go(NULL);
// Center the alert to this window and move down some
alert->CenterIn(Frame());
alert->MoveBy(0, kAddWindowOffset);
} else {
BMessage* addMessage = new BMessage(ADD_REPO_URL);
addMessage->AddString(key_url, url);
fReplyMessenger.SendMessage(addMessage);
Quit();
}
}
break;
}
default:
BWindow::MessageReceived(message);
}
}
void
AddRepoWindow::FrameResized(float newWidth, float newHeight)
{
sAddWindowWidth = newWidth;
}
status_t
AddRepoWindow::_GetClipboardData()
{
if (be_clipboard->Lock()) {
const char* string;
ssize_t stringLen;
BMessage* clip = be_clipboard->Data();
clip->FindData("text/plain", B_MIME_TYPE, (const void **)&string,
&stringLen);
be_clipboard->Unlock();
// The string must contain a web protocol
BString clipString(string, stringLen);
int32 ww = clipString.FindFirst("://");
if (ww == B_ERROR)
return B_ERROR;
else
fText->SetText(clipString);
}
return B_OK;
}
@@ -0,0 +1,35 @@
/*
* Copyright 2017 Haiku Inc. All rights reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
* Brian Hill
*/
#ifndef ADD_REPO_WINDOW_H
#define ADD_REPO_WINDOW_H
#include <Button.h>
#include <TextControl.h>
#include <View.h>
#include <Window.h>
class AddRepoWindow : public BWindow {
public:
AddRepoWindow(BRect size,
const BMessenger& messenger);
virtual void MessageReceived(BMessage*);
virtual void Quit();
virtual void FrameResized(float newWidth, float newHeight);
private:
BTextControl* fText;
BButton* fAddButton;
BButton* fCancelButton;
BMessenger fReplyMessenger;
status_t _GetClipboardData();
};
#endif
+31
View File
@@ -0,0 +1,31 @@
SubDir HAIKU_TOP src preferences repositories ;
UsePrivateHeaders interface ;
UsePrivateHeaders package ;
Preference Repositories :
AddRepoWindow.cpp
RepoRow.cpp
Repositories.cpp
RepositoriesView.cpp
RepositoriesWindow.cpp
RepositoriesSettings.cpp
TaskLooper.cpp
TaskTimer.cpp
: be package libcolumnlistview.a [ TargetLibstdc++ ] localestub
: Repositories.rdef
;
Depends Repositories : libcolumnlistview.a ;
DoCatalogs Repositories :
x-vnd.Haiku-Repositories
:
AddRepoWindow.cpp
constants.h
Repositories.cpp
RepositoriesView.cpp
RepositoriesWindow.cpp
TaskLooper.cpp
TaskTimer.cpp
;
+68
View File
@@ -0,0 +1,68 @@
/*
* Copyright 2017 Haiku Inc. All rights reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
* Brian Hill
*/
#include "RepoRow.h"
#include <ColumnTypes.h>
#include "constants.h"
RepoRow::RepoRow(const char* repo_name, const char* repo_url, bool enabled)
:
BRow(),
fName(repo_name),
fUrl(repo_url),
fEnabled(enabled),
fTaskState(STATE_NOT_IN_QUEUE)
{
SetField(new BStringField(""), kEnabledColumn);
SetField(new BStringField(fName.String()), kNameColumn);
SetField(new BStringField(fUrl.String()), kUrlColumn);
if (enabled)
SetEnabled(enabled);
}
void
RepoRow::SetName(const char* name)
{
BStringField* field = (BStringField*)GetField(kNameColumn);
field->SetString(name);
fName.SetTo(name);
Invalidate();
}
void
RepoRow::SetEnabled(bool enabled)
{
fEnabled = enabled;
RefreshEnabledField();
}
void
RepoRow::RefreshEnabledField()
{
BStringField* field = (BStringField*)GetField(kEnabledColumn);
if (fTaskState == STATE_NOT_IN_QUEUE)
field->SetString(fEnabled ? "\xE2\x9C\x94" : "");
else
field->SetString(B_UTF8_ELLIPSIS);
Invalidate();
}
void
RepoRow::SetTaskState(uint32 state)
{
fTaskState = state;
RefreshEnabledField();
}
+49
View File
@@ -0,0 +1,49 @@
/*
* Copyright 2017 Haiku Inc. All rights reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
* Brian Hill
*/
#ifndef REPO_ROW_H
#define REPO_ROW_H
#include <ColumnListView.h>
#include <String.h>
enum {
kEnabledColumn,
kNameColumn,
kUrlColumn
};
class RepoRow : public BRow {
public:
RepoRow(const char* repo_name,
const char* repo_url, bool enabled);
const char* Name() const { return fName.String(); }
void SetName(const char* name);
const char* Url() const { return fUrl.String(); }
void SetEnabled(bool enabled);
void RefreshEnabledField();
bool IsEnabled() { return fEnabled; }
void SetTaskState(uint32 state);
uint32 TaskState() { return fTaskState; }
void SetHasSiblings(bool hasSiblings)
{ fHasSiblings = hasSiblings; }
bool HasSiblings() { return fHasSiblings; }
private:
BString fName;
BString fUrl;
bool fEnabled;
uint32 fTaskState;
bool fHasSiblings;
};
#endif
@@ -0,0 +1,36 @@
/*
* Copyright 2017 Haiku Inc. All rights reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
* Brian Hill
*/
#include "Repositories.h"
#include <Catalog.h>
#include "constants.h"
#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "RepositoriesApplication"
const char* kAppSignature = "application/x-vnd.Haiku-Repositories";
RepositoriesApplication::RepositoriesApplication()
:
BApplication(kAppSignature)
{
fWindow = new RepositoriesWindow();
}
int
main()
{
RepositoriesApplication myApp;
myApp.Run();
return 0;
}
@@ -0,0 +1,26 @@
/*
* Copyright 2017 Haiku Inc. All rights reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
* Brian Hill
*/
#ifndef REPOSITORIES_H
#define REPOSITORIES_H
#include <Application.h>
#include "RepositoriesWindow.h"
class RepositoriesApplication : public BApplication {
public:
RepositoriesApplication();
private:
RepositoriesWindow* fWindow;
};
#endif
@@ -0,0 +1,43 @@
resource app_signature "application/x-vnd.Haiku-Repositories";
resource app_name_catalog_entry "x-vnd.Haiku-Repositories:System name:Repositories";
resource app_flags B_SINGLE_LAUNCH;
resource app_version {
major = 1,
middle = 0,
minor = 0,
/* 0 = development 1 = alpha 2 = beta
3 = gamma 4 = golden master 5 = final */
variety = 2,
internal = 0,
short_info = "Repositories",
long_info = "Repositories ©2017 Haiku"
};
resource vector_icon {
$"6E6369660C03010000020006023B9FE037664CBA16573E39B04A01E3449F7E00"
$"FFFFFFFFEBEFFF020006023C96323A4D3FBAFC013D5A974B57A549844D00C1CC"
$"FFFFFFFFFF02000602BA40DA3C98EBBD5E1FBAEBF04A3DF04AD89600C1CCFFFF"
$"FDFDFD0401800500020006023C43C6B9E5E23A85A83CEE414268F44A445900C6"
$"D7F5FF6B94DD020006023C71E33A0C78BA15E43C7D2149055549455700E3EDFF"
$"FF9EC2FF03003CB0030D29640401740401D30B0A062235224044525A3A5A3139"
$"250A04223544465A3139250A04444644525A3A5A310A0422352240445244460A"
$"0544544955603C593A593C0A05305E376046513B4E3E510A0622422254325C3E"
$"513E402E3A0A0422422254325C32490A04224232493E402E3A0A043249325C3E"
$"513E400604672645264426464B284C46120A04010420202B0A00010030202B01"
$"178300040A01010120202B0A02010220202B0A03010320202B0A0A010502403F"
$"3500000000000040268D4707E6C919420A0501061A403F350000000000004026"
$"8D4707E6C9194215FF01178400040A0501061A403F3500000000000040268D47"
$"07E6C91942001501178600040A06010702403F3500000000000040268D4707E6"
$"C919420A08010902403F3500000000000040268D4707E6C919420A0701080240"
$"3F3500000000000040268D4707E6C919420A0B010A000A0B010A2024220A0B01"
$"0A2028240A0B010A202C260A0B010A2030280A0B010A20342A0A0B010A20382C"
};
@@ -0,0 +1,128 @@
/*
* Copyright 2017 Haiku Inc. All rights reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
* Brian Hill
*/
#include "RepositoriesSettings.h"
#include <FindDirectory.h>
#include <StringList.h>
#include "constants.h"
const char* settingsFilename = "Repositories_settings";
RepositoriesSettings::RepositoriesSettings()
{
status_t status = find_directory(B_USER_SETTINGS_DIRECTORY, &fFilePath);
if (status == B_OK)
status = fFilePath.Append(settingsFilename);
BEntry fileEntry(fFilePath.Path());
if (!fileEntry.Exists()) {
// Create default repos
BStringList nameList, urlList;
int32 count = (sizeof(kDefaultRepos) / sizeof(Repository));
for (int16 index = 0; index < count; index++) {
nameList.Add(kDefaultRepos[index].name);
urlList.Add(kDefaultRepos[index].url);
}
SetRepositories(nameList, urlList);
}
fInitStatus = status;
}
BRect
RepositoriesSettings::GetFrame()
{
BMessage settings(_ReadFromFile());
BRect frame;
status_t status = settings.FindRect(key_frame, &frame);
// Set default off screen so it will center itself
if (status != B_OK)
frame.Set(-10, -10, 750, 300);
return frame;
}
void
RepositoriesSettings::SetFrame(BRect frame)
{
BMessage settings(_ReadFromFile());
settings.RemoveData(key_frame);
settings.AddRect(key_frame, frame);
_SaveToFile(settings);
}
status_t
RepositoriesSettings::GetRepositories(int32& repoCount, BStringList& nameList,
BStringList& urlList)
{
BMessage settings(_ReadFromFile());
type_code type;
int32 count;
settings.GetInfo(key_name, &type, &count);
status_t result = B_OK;
int32 index, total = 0;
BString foundName, foundUrl;
// get each repository and add to lists
for (index = 0; index < count; index++) {
status_t result1 = settings.FindString(key_name, index, &foundName);
status_t result2 = settings.FindString(key_url, index, &foundUrl);
if (result1 == B_OK && result2 == B_OK) {
nameList.Add(foundName);
urlList.Add(foundUrl);
total++;
} else
result = B_ERROR;
}
repoCount = total;
return result;
}
void
RepositoriesSettings::SetRepositories(BStringList& nameList, BStringList& urlList)
{
BMessage settings(_ReadFromFile());
settings.RemoveName(key_name);
settings.RemoveName(key_url);
int32 index, count = nameList.CountStrings();
for (index = 0; index < count; index++) {
settings.AddString(key_name, nameList.StringAt(index));
settings.AddString(key_url, urlList.StringAt(index));
}
_SaveToFile(settings);
}
BMessage
RepositoriesSettings::_ReadFromFile()
{
BMessage settings;
status_t status = fFile.SetTo(fFilePath.Path(), B_READ_ONLY);
if (status == B_OK)
status = settings.Unflatten(&fFile);
fFile.Unset();
return settings;
}
status_t
RepositoriesSettings::_SaveToFile(BMessage settings)
{
status_t status = fFile.SetTo(fFilePath.Path(),
B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE);
if (status == B_OK)
status = settings.Flatten(&fFile);
fFile.Unset();
return status;
}
@@ -0,0 +1,40 @@
/*
* Copyright 2017 Haiku Inc. All rights reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
* Brian Hill
*/
#ifndef REPOSITORIES_SETTINGS_H
#define REPOSITORIES_SETTINGS_H
#include <File.h>
#include <Message.h>
#include <Path.h>
#include <Point.h>
#include <Rect.h>
#include <String.h>
#include <StringList.h>
class RepositoriesSettings {
public:
RepositoriesSettings();
BRect GetFrame();
void SetFrame(BRect frame);
status_t GetRepositories(int32& repoCount,
BStringList& nameList, BStringList& urlList);
void SetRepositories(BStringList& nameList,
BStringList& urlList);
private:
BPath fFilePath;
BFile fFile;
status_t fInitStatus;
BMessage _ReadFromFile();
status_t _SaveToFile(BMessage settings);
};
#endif
@@ -0,0 +1,758 @@
/*
* Copyright 2017 Haiku Inc. All rights reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
* Brian Hill
*/
#include "RepositoriesView.h"
#include <stdlib.h>
#include <Alert.h>
#include <Button.h>
#include <Catalog.h>
#include <ColumnTypes.h>
#include <LayoutBuilder.h>
#include <MessageRunner.h>
#include <ScrollBar.h>
#include <SeparatorView.h>
#include <package/PackageRoster.h>
#include <package/RepositoryConfig.h>
#include "constants.h"
#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "RepositoriesView"
static const BString kTitleEnabled =
B_TRANSLATE_COMMENT("Enabled", "Column title");
static const BString kTitleName = B_TRANSLATE_COMMENT("Name", "Column title");
static const BString kTitleUrl = B_TRANSLATE_COMMENT("URL", "Column title");
static const BString kLabelRemove =
B_TRANSLATE_COMMENT("Remove", "Button label");
static const BString kLabelRemoveAll =
B_TRANSLATE_COMMENT("Remove All", "Button label");
static const BString kLabelEnable =
B_TRANSLATE_COMMENT("Enable", "Button label");
static const BString kLabelEnableAll =
B_TRANSLATE_COMMENT("Enable All", "Button label");
static const BString kLabelDisable =
B_TRANSLATE_COMMENT("Disable", "Button label");
static const BString kLabelDisableAll =
B_TRANSLATE_COMMENT("Disable All", "Button label");
static const BString kStatusViewText =
B_TRANSLATE_COMMENT("Changes pending:", "Status view text");
static const BString kStatusCompletedText =
B_TRANSLATE_COMMENT("Changes completed", "Status view text");
RepositoriesListView::RepositoriesListView(const char* name)
:
BColumnListView(name, B_NAVIGABLE, B_PLAIN_BORDER)
{
}
void
RepositoriesListView::KeyDown(const char* bytes, int32 numBytes)
{
switch (bytes[0]) {
case B_DELETE:
Window()->PostMessage(DELETE_KEY_PRESSED);
break;
default:
BColumnListView::KeyDown(bytes, numBytes);
}
}
RepositoriesView::RepositoriesView()
:
BGroupView("RepositoriesView"),
fTaskLooper(NULL),
fShowCompletedStatus(false),
fRunningTaskCount(0),
fLastCompletedTimerId(0)
{
// Column list view with 3 columns
fListView = new RepositoriesListView("list");
fListView->SetSelectionMessage(new BMessage(LIST_SELECTION_CHANGED));
float col0width = be_plain_font->StringWidth(kTitleEnabled) + 15;
float col1width = be_plain_font->StringWidth(kTitleName) + 15;
float col2width = be_plain_font->StringWidth(kTitleUrl) + 15;
fListView->AddColumn(new BStringColumn(kTitleEnabled, col0width, col0width,
col0width, B_TRUNCATE_END, B_ALIGN_CENTER), kEnabledColumn);
fListView->AddColumn(new BStringColumn(kTitleName, 90, col1width, 300,
B_TRUNCATE_END), kNameColumn);
fListView->AddColumn(new BStringColumn(kTitleUrl, 500, col2width, 5000,
B_TRUNCATE_END), kUrlColumn);
fListView->SetInvocationMessage(new BMessage(ITEM_INVOKED));
// Repository list status view
fStatusContainerView = new BView("status", B_SUPPORTS_LAYOUT);
BString templateText(kStatusViewText);
templateText.Append(" 88");
// Simulate a status text with two digit queue count
fListStatusView = new BStringView("status", templateText);
// Set a smaller fixed font size and slightly lighten text color
BFont font(be_plain_font);
font.SetSize(10.0f);
fListStatusView->SetFont(&font, B_FONT_SIZE);
fListStatusView->SetHighUIColor(fListStatusView->HighUIColor(), .9f);
// Set appropriate explicit view sizes
float viewWidth = max(fListStatusView->StringWidth(templateText),
fListStatusView->StringWidth(kStatusCompletedText));
BSize statusViewSize(viewWidth + 3, B_H_SCROLL_BAR_HEIGHT - 2);
fListStatusView->SetExplicitSize(statusViewSize);
statusViewSize.height += 1;
fStatusContainerView->SetExplicitSize(statusViewSize);
BLayoutBuilder::Group<>(fStatusContainerView, B_HORIZONTAL, 0)
.Add(new BSeparatorView(B_VERTICAL))
.AddGroup(B_VERTICAL, 0)
.AddGlue()
.AddGroup(B_HORIZONTAL, 0)
.SetInsets(2, 0, 0, 0)
.Add(fListStatusView)
.AddGlue()
.End()
.Add(new BSeparatorView(B_HORIZONTAL))
.End()
.End();
fListView->AddStatusView(fStatusContainerView);
// Standard buttons
fEnableButton = new BButton(kLabelEnable,
new BMessage(ENABLE_BUTTON_PRESSED));
fDisableButton = new BButton(kLabelDisable,
new BMessage(DISABLE_BUTTON_PRESSED));
// Create buttons with fixed size
font_height fontHeight;
GetFontHeight(&fontHeight);
int16 buttonHeight = int16(fontHeight.ascent + fontHeight.descent + 12);
// button size determined by font size
BSize btnSize(buttonHeight, buttonHeight);
fAddButton = new BButton("plus", "+", new BMessage(ADD_REPO_WINDOW));
fAddButton->SetExplicitSize(btnSize);
fRemoveButton = new BButton("minus", "-", new BMessage(REMOVE_REPOS));
fRemoveButton->SetExplicitSize(btnSize);
// Layout
int16 buttonSpacing = 1;
BLayoutBuilder::Group<>(this, B_VERTICAL, 0)
.SetInsets(B_USE_WINDOW_SPACING)
.AddGroup(B_HORIZONTAL, 0, 0.0)
.Add(new BStringView("instruction", B_TRANSLATE_COMMENT("Select"
" repositories to use with Haiku package management:",
"Label text")), 0.0)
.AddGlue()
.End()
.AddStrut(B_USE_DEFAULT_SPACING)
.Add(fListView, 1)
.AddGroup(B_HORIZONTAL, 0, 0.0)
// Add and Remove buttons
.AddGroup(B_VERTICAL, 0, 0.0)
.AddGroup(B_HORIZONTAL, 0, 0.0)
.Add(new BSeparatorView(B_VERTICAL))
.AddGroup(B_VERTICAL, 0, 0.0)
.AddGroup(B_HORIZONTAL, buttonSpacing, 0.0)
.SetInsets(buttonSpacing)
.Add(fAddButton)
.Add(fRemoveButton)
.End()
.Add(new BSeparatorView(B_HORIZONTAL))
.End()
.Add(new BSeparatorView(B_VERTICAL))
.End()
.AddGlue()
.End()
// Enable and Disable buttons
.AddGroup(B_HORIZONTAL)
.SetInsets(B_USE_DEFAULT_SPACING, B_USE_DEFAULT_SPACING,
B_USE_DEFAULT_SPACING, 0)
.AddGlue()
.Add(fEnableButton)
.Add(fDisableButton)
.End()
.End()
.End();
}
RepositoriesView::~RepositoriesView()
{
if (fTaskLooper) {
fTaskLooper->Lock();
fTaskLooper->Quit();
}
_EmptyList();
}
void
RepositoriesView::AllAttached()
{
BView::AllAttached();
fRemoveButton->SetTarget(this);
fEnableButton->SetTarget(this);
fDisableButton->SetTarget(this);
fListView->SetTarget(this);
fRemoveButton->SetEnabled(false);
fEnableButton->SetEnabled(false);
fDisableButton->SetEnabled(false);
_UpdateStatusView();
_InitList();
}
void
RepositoriesView::AttachedToWindow()
{
fTaskLooper = new TaskLooper(BMessenger(this));
}
void
RepositoriesView::MessageReceived(BMessage* message)
{
switch (message->what)
{
case REMOVE_REPOS: {
RepoRow* rowItem = dynamic_cast<RepoRow*>(fListView->CurrentSelection());
if (!rowItem || !fRemoveButton->IsEnabled())
break;
BString text;
// More than one selected row
if (fListView->CurrentSelection(rowItem)) {
text.SetTo(B_TRANSLATE_COMMENT("Remove these repositories?",
"Removal alert confirmation message"));
text.Append("\n");
}
// Only one selected row
else {
text.SetTo(B_TRANSLATE_COMMENT("Remove this repository?",
"Removal alert confirmation message"));
text.Append("\n");
}
float minWidth = 0;
while (rowItem) {
BString repoText;
repoText.Append("\n").Append(rowItem->Name())
.Append(" (").Append(rowItem->Url()).Append(")");
minWidth = max(minWidth, StringWidth(repoText.String()));
text.Append(repoText);
rowItem = dynamic_cast<RepoRow*>(fListView->CurrentSelection(rowItem));
}
minWidth = min(minWidth, Frame().Width());
// Ensure alert window isn't much larger than the main window
BAlert* alert = new BAlert("confirm", text, kRemoveLabel,
kCancelLabel, NULL, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
alert->TextView()->SetExplicitMinSize(BSize(minWidth, B_SIZE_UNSET));
alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
int32 answer = alert->Go();
// User presses Cancel button
if (answer)
break;
rowItem = dynamic_cast<RepoRow*>(fListView->CurrentSelection());
while (rowItem) {
RepoRow* oldRow = rowItem;
rowItem = dynamic_cast<RepoRow*>(fListView->CurrentSelection(rowItem));
fListView->RemoveRow(oldRow);
delete oldRow;
}
_SaveList();
break;
}
case LIST_SELECTION_CHANGED:
_UpdateButtons();
break;
case ITEM_INVOKED: {
// Simulates pressing whichever is the enabled button
if (fEnableButton->IsEnabled()) {
BMessage invokeMessage(ENABLE_BUTTON_PRESSED);
MessageReceived(&invokeMessage);
} else if (fDisableButton->IsEnabled()) {
BMessage invokeMessage(DISABLE_BUTTON_PRESSED);
MessageReceived(&invokeMessage);
}
break;
}
case ENABLE_BUTTON_PRESSED: {
BStringList names;
bool paramsOK = true;
// Check if there are multiple selections of the same repository,
// pkgman won't like that
RepoRow* rowItem = dynamic_cast<RepoRow*>(fListView->CurrentSelection());
while (rowItem) {
if (names.HasString(rowItem->Name())
&& kNewRepoDefaultName.Compare(rowItem->Name()) != 0) {
(new BAlert("duplicate",
B_TRANSLATE_COMMENT("Only one URL for each repository can "
"be enabled. Please change your selections.",
"Error message"),
kOKLabel, NULL, NULL,
B_WIDTH_AS_USUAL, B_STOP_ALERT))->Go(NULL);
paramsOK = false;
break;
} else
names.Add(rowItem->Name());
rowItem = dynamic_cast<RepoRow*>(fListView->CurrentSelection(rowItem));
}
if (paramsOK) {
_AddSelectedRowsToQueue();
_UpdateButtons();
}
break;
}
case DISABLE_BUTTON_PRESSED:
_AddSelectedRowsToQueue();
_UpdateButtons();
break;
case TASK_STARTED: {
int16 count;
status_t result1 = message->FindInt16(key_count, &count);
RepoRow* rowItem;
status_t result2 = message->FindPointer(key_rowptr, (void**)&rowItem);
if (result1 == B_OK && result2 == B_OK)
_TaskStarted(rowItem, count);
break;
}
case TASK_COMPLETED_WITH_ERRORS: {
BString errorDetails;
status_t result = message->FindString(key_details, &errorDetails);
if (result == B_OK) {
(new BAlert("error", errorDetails, kOKLabel, NULL, NULL,
B_WIDTH_AS_USUAL, B_STOP_ALERT))->Go(NULL);
}
BString repoName = message->GetString(key_name,
kNewRepoDefaultName.String());
int16 count;
status_t result1 = message->FindInt16(key_count, &count);
RepoRow* rowItem;
status_t result2 = message->FindPointer(key_rowptr, (void**)&rowItem);
if (result1 == B_OK && result2 == B_OK) {
_TaskCompleted(rowItem, count, repoName);
// Refresh the enabled status of each row since it is unsure what
// caused the error
_RefreshList();
}
_UpdateButtons();
break;
}
case TASK_COMPLETED: {
BString repoName = message->GetString(key_name,
kNewRepoDefaultName.String());
int16 count;
status_t result1 = message->FindInt16(key_count, &count);
RepoRow* rowItem;
status_t result2 = message->FindPointer(key_rowptr, (void**)&rowItem);
if (result1 == B_OK && result2 == B_OK) {
_TaskCompleted(rowItem, count, repoName);
// If the completed row has siblings then enabling this row may
// have disabled one of the other siblings, do full refresh.
if (rowItem->HasSiblings() && rowItem->IsEnabled())
_RefreshList();
}
_UpdateButtons();
break;
}
case TASK_CANCELED: {
int16 count;
status_t result1 = message->FindInt16(key_count, &count);
RepoRow* rowItem;
status_t result2 = message->FindPointer(key_rowptr, (void**)&rowItem);
if (result1 == B_OK && result2 == B_OK)
_TaskCanceled(rowItem, count);
// Refresh the enabled status of each row since it is unsure what
// caused the cancelation
_RefreshList();
_UpdateButtons();
break;
}
case UPDATE_LIST:
_RefreshList();
_UpdateButtons();
break;
case STATUS_VIEW_COMPLETED_TIMEOUT: {
int32 timerID;
status_t result = message->FindInt32(key_ID, &timerID);
if (result == B_OK && timerID == fLastCompletedTimerId)
_UpdateStatusView();
break;
}
default:
BView::MessageReceived(message);
}
}
void
RepositoriesView::_AddSelectedRowsToQueue()
{
RepoRow* rowItem = dynamic_cast<RepoRow*>(fListView->CurrentSelection());
while (rowItem) {
rowItem->SetTaskState(STATE_IN_QUEUE_WAITING);
BMessage taskMessage(DO_TASK);
taskMessage.AddPointer(key_rowptr, rowItem);
fTaskLooper->PostMessage(&taskMessage);
rowItem = dynamic_cast<RepoRow*>(fListView->CurrentSelection(rowItem));
}
}
void
RepositoriesView::_TaskStarted(RepoRow* rowItem, int16 count)
{
fRunningTaskCount = count;
rowItem->SetTaskState(STATE_IN_QUEUE_RUNNING);
// Only present a status count if there is more than one task in queue
if (count > 1) {
_UpdateStatusView();
fShowCompletedStatus = true;
}
}
void
RepositoriesView::_TaskCompleted(RepoRow* rowItem, int16 count, BString& newName)
{
fRunningTaskCount = count;
_ShowCompletedStatusIfDone();
// Update row state and values
rowItem->SetTaskState(STATE_NOT_IN_QUEUE);
if (kNewRepoDefaultName.Compare(rowItem->Name()) == 0
&& newName.Compare("") != 0)
rowItem->SetName(newName.String());
_UpdateFromRepoConfig(rowItem);
}
void
RepositoriesView::_TaskCanceled(RepoRow* rowItem, int16 count)
{
fRunningTaskCount = count;
_ShowCompletedStatusIfDone();
// Update row state and values
rowItem->SetTaskState(STATE_NOT_IN_QUEUE);
_UpdateFromRepoConfig(rowItem);
}
void
RepositoriesView::_ShowCompletedStatusIfDone()
{
// If this is the last task show completed status text for 3 seconds
if (fRunningTaskCount == 0 && fShowCompletedStatus) {
fListStatusView->SetText(kStatusCompletedText);
fLastCompletedTimerId = rand();
BMessage timerMessage(STATUS_VIEW_COMPLETED_TIMEOUT);
timerMessage.AddInt32(key_ID, fLastCompletedTimerId);
new BMessageRunner(this, &timerMessage, 3000000, 1);
fShowCompletedStatus = false;
} else
_UpdateStatusView();
}
void
RepositoriesView::_UpdateFromRepoConfig(RepoRow* rowItem)
{
BPackageKit::BPackageRoster pRoster;
BPackageKit::BRepositoryConfig repoConfig;
BString repoName(rowItem->Name());
status_t result = pRoster.GetRepositoryConfig(repoName, &repoConfig);
// Repo name was found and the URL matches
if (result == B_OK && repoConfig.BaseURL() == rowItem->Url())
rowItem->SetEnabled(true);
else
rowItem->SetEnabled(false);
}
void
RepositoriesView::AddManualRepository(BString url)
{
BString name(kNewRepoDefaultName);
BString rootUrl = _GetRootUrl(url);
bool foundRoot = false;
int32 index;
int32 listCount = fListView->CountRows();
for (index = 0; index < listCount; index++) {
RepoRow* repoItem = dynamic_cast<RepoRow*>(fListView->RowAt(index));
const char* urlPtr = repoItem->Url();
// Find an already existing URL
if (url.ICompare(urlPtr) == 0) {
(new BAlert("duplicate",
B_TRANSLATE_COMMENT("This repository URL already exists.",
"Error message"),
kOKLabel))->Go(NULL);
return;
}
// Use the same name from another repo with the same root url
if (foundRoot == false && rootUrl.ICompare(urlPtr,
rootUrl.Length()) == 0) {
foundRoot = true;
name = repoItem->Name();
}
}
RepoRow* newRepo = _AddRepo(name, url, false);
_FindSiblings();
fListView->DeselectAll();
fListView->AddToSelection(newRepo);
_UpdateButtons();
_SaveList();
}
BString
RepositoriesView::_GetRootUrl(BString url)
{
// Find the protocol if it exists
int32 ww = url.FindFirst("://");
if (ww == B_ERROR)
ww = 0;
else
ww += 3;
// Find second /
int32 rootEnd = url.FindFirst("/", ww + 1);
if (rootEnd == B_ERROR)
return url;
rootEnd = url.FindFirst("/", rootEnd + 1);
if (rootEnd == B_ERROR)
return url;
else
return url.Truncate(rootEnd);
}
status_t
RepositoriesView::_EmptyList()
{
BRow* row;
while ((row = fListView->RowAt((int32)0, NULL)) != NULL) {
fListView->RemoveRow(row);
delete row;
}
return B_OK;
}
void
RepositoriesView::_InitList()
{
// Get list of known repositories from the settings file
int32 index, repoCount;
BStringList nameList, urlList;
status_t result = fSettings.GetRepositories(repoCount, nameList, urlList);
if (result == B_OK) {
BString name, url;
for (index = 0; index < repoCount; index++) {
name = nameList.StringAt(index);
url = urlList.StringAt(index);
_AddRepo(name, url, false);
}
}
_UpdateListFromRoster();
fListView->SetSortColumn(fListView->ColumnAt(kUrlColumn), false, true);
fListView->ResizeAllColumnsToPreferred();
}
void
RepositoriesView::_RefreshList()
{
// Clear enabled status on all rows
int32 index, listCount = fListView->CountRows();
for (index = 0; index < listCount; index++) {
RepoRow* repoItem = dynamic_cast<RepoRow*>(fListView->RowAt(index));
if (repoItem->TaskState() == STATE_NOT_IN_QUEUE)
repoItem->SetEnabled(false);
}
// Get current list of enabled repositories
_UpdateListFromRoster();
}
void
RepositoriesView::_UpdateListFromRoster()
{
// Get list of currently enabled repositories
BStringList repositoryNames;
BPackageKit::BPackageRoster pRoster;
status_t result = pRoster.GetRepositoryNames(repositoryNames);
if (result != B_OK) {
(new BAlert("error",
B_TRANSLATE_COMMENT("Repositories could not retrieve the names of "
"the currently enabled repositories.", "Alert error message"),
kOKLabel, NULL, NULL, B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go(NULL);
return;
}
BPackageKit::BRepositoryConfig repoConfig;
int16 index, count = repositoryNames.CountStrings();
for (index = 0; index < count; index++) {
const BString& repoName = repositoryNames.StringAt(index);
result = pRoster.GetRepositoryConfig(repoName, &repoConfig);
if (result == B_OK)
_AddRepo(repoName, repoConfig.BaseURL(), true);
else {
BString text(B_TRANSLATE_COMMENT("Error getting repository"
" configuration for %name%.", "Alert error message, "
"do not translate %name%"));
text.ReplaceFirst("%name%", repoName);
(new BAlert("error", text, kOKLabel))->Go(NULL);
}
}
_FindSiblings();
_SaveList();
}
void
RepositoriesView::_SaveList()
{
BStringList nameList, urlList;
int32 index;
int32 listCount = fListView->CountRows();
for (index = 0; index < listCount; index++) {
RepoRow* repoItem = dynamic_cast<RepoRow*>(fListView->RowAt(index));
nameList.Add(repoItem->Name());
urlList.Add(repoItem->Url());
}
fSettings.SetRepositories(nameList, urlList);
}
RepoRow*
RepositoriesView::_AddRepo(BString name, BString url, bool enabled)
{
// URL must have a protocol
if (url.FindFirst("://") == B_ERROR)
return NULL;
RepoRow* addedRow = NULL;
int32 index;
int32 listCount = fListView->CountRows();
// Find if the repo already exists in list
for (index = 0; index < listCount; index++) {
RepoRow* repoItem = dynamic_cast<RepoRow*>(fListView->RowAt(index));
if (url.ICompare(repoItem->Url()) == 0) {
// update name and enabled values
if (name.Compare(repoItem->Name()) != 0)
repoItem->SetName(name.String());
repoItem->SetEnabled(enabled);
addedRow = repoItem;
}
}
if (addedRow == NULL) {
addedRow = new RepoRow(name, url, enabled);
fListView->AddRow(addedRow);
}
return addedRow;
}
void
RepositoriesView::_FindSiblings()
{
BStringList namesFound, namesWithSiblings;
int32 index, listCount = fListView->CountRows();
// Find repository names that are duplicated
for (index = 0; index < listCount; index++) {
RepoRow* repoItem = dynamic_cast<RepoRow*>(fListView->RowAt(index));
BString name = repoItem->Name();
// Ignore newly added repos since we don't know the real name yet
if (name.Compare(kNewRepoDefaultName)==0)
continue;
// First time a name is found- no sibling (yet)
if (!namesFound.HasString(name))
namesFound.Add(name);
// Name was already found once so this name has 2 or more siblings
else if (!namesWithSiblings.HasString(name))
namesWithSiblings.Add(name);
}
// Set sibling values for each row
for (index = 0; index < listCount; index++) {
RepoRow* repoItem = dynamic_cast<RepoRow*>(fListView->RowAt(index));
BString name = repoItem->Name();
repoItem->SetHasSiblings(namesWithSiblings.HasString(name));
}
}
void
RepositoriesView::_UpdateButtons()
{
RepoRow* rowItem = dynamic_cast<RepoRow*>(fListView->CurrentSelection());
// At least one row is selected
if (rowItem) {
bool someAreEnabled = false,
someAreDisabled = false,
someAreInQueue = false;
int32 selectedCount = 0;
RepoRow* rowItem = dynamic_cast<RepoRow*>(fListView->CurrentSelection());
while (rowItem) {
selectedCount++;
uint32 taskState = rowItem->TaskState();
if ( taskState == STATE_IN_QUEUE_WAITING
|| taskState == STATE_IN_QUEUE_RUNNING)
someAreInQueue = true;
if (rowItem->IsEnabled())
someAreEnabled = true;
else
someAreDisabled = true;
rowItem = dynamic_cast<RepoRow*>(fListView->CurrentSelection(rowItem));
}
// Change button labels depending on which rows are selected
if (selectedCount > 1) {
fEnableButton->SetLabel(kLabelEnableAll);
fDisableButton->SetLabel(kLabelDisableAll);
} else {
fEnableButton->SetLabel(kLabelEnable);
fDisableButton->SetLabel(kLabelDisable);
}
// Set which buttons should be enabled
fRemoveButton->SetEnabled(!someAreEnabled && !someAreInQueue);
if ((someAreEnabled && someAreDisabled) || someAreInQueue) {
// there are a mix of enabled and disabled repositories selected
fEnableButton->SetEnabled(false);
fDisableButton->SetEnabled(false);
} else {
fEnableButton->SetEnabled(someAreDisabled);
fDisableButton->SetEnabled(someAreEnabled);
}
} else {
// No selected rows
fEnableButton->SetLabel(kLabelEnable);
fDisableButton->SetLabel(kLabelDisable);
fEnableButton->SetEnabled(false);
fDisableButton->SetEnabled(false);
fRemoveButton->SetEnabled(false);
}
}
void
RepositoriesView::_UpdateStatusView()
{
if (fRunningTaskCount) {
BString text(kStatusViewText);
text.Append(" ");
text << fRunningTaskCount;
fListStatusView->SetText(text);
} else
fListStatusView->SetText("");
}
@@ -0,0 +1,76 @@
/*
* Copyright 2017 Haiku Inc. All rights reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
* Brian Hill
*/
#ifndef REPOSITORIES_VIEW_H
#define REPOSITORIES_VIEW_H
#include <ColumnListView.h>
#include <GroupView.h>
#include <String.h>
#include <StringView.h>
#include <View.h>
#include "RepositoriesSettings.h"
#include "RepoRow.h"
#include "TaskLooper.h"
class RepositoriesListView : public BColumnListView {
public:
RepositoriesListView(const char* name);
virtual void KeyDown(const char* bytes, int32 numBytes);
};
class RepositoriesView : public BGroupView {
public:
RepositoriesView();
~RepositoriesView();
virtual void AllAttached();
virtual void AttachedToWindow();
virtual void MessageReceived(BMessage*);
void AddManualRepository(BString url);
bool IsTaskRunning() { return fRunningTaskCount > 0; }
private:
RepositoriesSettings fSettings;
RepositoriesListView* fListView;
BView* fStatusContainerView;
BStringView* fListStatusView;
TaskLooper* fTaskLooper;
bool fShowCompletedStatus;
int fRunningTaskCount, fLastCompletedTimerId;
BButton* fAddButton;
BButton* fRemoveButton;
BButton* fEnableButton;
BButton* fDisableButton;
// Message helpers
void _AddSelectedRowsToQueue();
void _TaskStarted(RepoRow* rowItem, int16 count);
void _TaskCompleted(RepoRow* rowItem, int16 count,
BString& newName);
void _TaskCanceled(RepoRow* rowItem, int16 count);
void _ShowCompletedStatusIfDone();
void _UpdateFromRepoConfig(RepoRow* rowItem);
// GUI functions
BString _GetRootUrl(BString url);
status_t _EmptyList();
void _InitList();
void _RefreshList();
void _UpdateListFromRoster();
void _SaveList();
RepoRow* _AddRepo(BString name, BString url, bool enabled);
void _FindSiblings();
void _UpdateButtons();
void _UpdateStatusView();
};
#endif
@@ -0,0 +1,173 @@
/*
* Copyright 2017 Haiku Inc. All rights reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
* Brian Hill
*/
#include "RepositoriesWindow.h"
#include <Alert.h>
#include <Application.h>
#include <Catalog.h>
#include <FindDirectory.h>
#include <LayoutBuilder.h>
#include <NodeMonitor.h>
#include <Region.h>
#include <Screen.h>
#include "AddRepoWindow.h"
#include "constants.h"
#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "RepositoriesWindow"
RepositoriesWindow::RepositoriesWindow()
:
BWindow(BRect(50, 50, 500, 400), B_TRANSLATE_SYSTEM_NAME("Repositories"),
B_TITLED_WINDOW, B_NOT_ZOOMABLE | B_ASYNCHRONOUS_CONTROLS
| B_AUTO_UPDATE_SIZE_LIMITS),
fAddWindow(NULL),
fPackageNodeStatus(B_ERROR)
{
fView = new RepositoriesView();
BLayoutBuilder::Group<>(this, B_VERTICAL).Add(fView).End();
// Size and location on screen
BRect frame = fSettings.GetFrame();
ResizeTo(frame.Width(), frame.Height());
BScreen screen;
BRect screenFrame = screen.Frame();
if (screenFrame.right < frame.right || screenFrame.left > frame.left
|| screenFrame.top > frame.top || screenFrame.bottom < frame.bottom)
CenterOnScreen();
else
MoveTo(frame.left, frame.top);
Show();
fMessenger.SetTo(this);
// Find the pkgman settings or cache directory
BPath packagePath;
// /boot/system/settings/package-repositories
status_t status = find_directory(B_SYSTEM_SETTINGS_DIRECTORY,
&packagePath);
if (status == B_OK)
status = packagePath.Append("package-repositories");
else {
// /boot/system/cache/package-repositories
status = find_directory(B_SYSTEM_CACHE_DIRECTORY, &packagePath);
if (status == B_OK)
status = packagePath.Append("package-repositories");
}
if (status == B_OK) {
BNode packageNode(packagePath.Path());
if (packageNode.InitCheck()==B_OK && packageNode.IsDirectory())
fPackageNodeStatus = packageNode.GetNodeRef(&fPackageNodeRef);
}
// watch the pkgman settings or cache directory for changes
_StartWatching();
}
RepositoriesWindow::~RepositoriesWindow()
{
_StopWatching();
}
void
RepositoriesWindow::_StartWatching()
{
if (fPackageNodeStatus == B_OK) {
status_t result = watch_node(&fPackageNodeRef, B_WATCH_DIRECTORY, this);
fWatchingPackageNode = (result == B_OK);
}
}
void
RepositoriesWindow::_StopWatching()
{
if (fPackageNodeStatus == B_OK && fWatchingPackageNode) {
watch_node(&fPackageNodeRef, B_STOP_WATCHING, this);
fWatchingPackageNode = false;
}
}
bool
RepositoriesWindow::QuitRequested()
{
if (fView->IsTaskRunning()) {
BAlert *alert = new BAlert("tasks",
B_TRANSLATE_COMMENT("Some tasks are still running. Stop these "
"tasks and quit?", "Application quit alert message"),
B_TRANSLATE_COMMENT("Stop and quit", "Button label"),
kCancelLabel, NULL, B_WIDTH_AS_USUAL, B_STOP_ALERT);
alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
int32 result = alert->Go();
if (result != 0)
return false;
}
fSettings.SetFrame(Frame());
be_app->PostMessage(B_QUIT_REQUESTED);
return BWindow::QuitRequested();
}
void
RepositoriesWindow::MessageReceived(BMessage* message)
{
switch (message->what)
{
case ADD_REPO_WINDOW: {
BRect frame = Frame();
fAddWindow = new AddRepoWindow(frame, fMessenger);
break;
}
case ADD_REPO_URL: {
BString url;
status_t result = message->FindString(key_url, &url);
if (result == B_OK)
fView->AddManualRepository(url);
break;
}
case ADD_WINDOW_CLOSED: {
fAddWindow = NULL;
break;
}
case DELETE_KEY_PRESSED: {
BMessage message(REMOVE_REPOS);
fView->MessageReceived(&message);
break;
}
// captures pkgman changes while the Repositories application is running
case B_NODE_MONITOR: {
// This preflet is making the changes, so ignore this message
if (fView->IsTaskRunning())
break;
int32 opcode;
if (message->FindInt32("opcode", &opcode) == B_OK) {
switch (opcode)
{
case B_ATTR_CHANGED:
case B_ENTRY_CREATED:
case B_ENTRY_REMOVED: {
PostMessage(UPDATE_LIST, fView);
break;
}
}
}
break;
}
default:
BWindow::MessageReceived(message);
}
}
@@ -0,0 +1,42 @@
/*
* Copyright 2017 Haiku Inc. All rights reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
* Brian Hill
*/
#ifndef REPOSITORIES_WINDOW_H
#define REPOSITORIES_WINDOW_H
#include <Node.h>
#include <Window.h>
#include "AddRepoWindow.h"
#include "RepositoriesSettings.h"
#include "RepositoriesView.h"
class RepositoriesWindow : public BWindow {
public:
RepositoriesWindow();
~RepositoriesWindow();
virtual bool QuitRequested();
virtual void MessageReceived(BMessage*);
private:
RepositoriesSettings fSettings;
RepositoriesView* fView;
AddRepoWindow* fAddWindow;
BMessenger fMessenger;
node_ref fPackageNodeRef;
// node_ref to watch for changes to package-repositories directory
status_t fPackageNodeStatus;
bool fWatchingPackageNode;
// true when package-repositories directory is being watched
void _StartWatching();
void _StopWatching();
};
#endif
+328
View File
@@ -0,0 +1,328 @@
/*
* Copyright 2017 Haiku Inc. All rights reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
* Brian Hill
*/
#include "TaskLooper.h"
#include <Catalog.h>
#include <MessageQueue.h>
#include <package/AddRepositoryRequest.h>
#include <package/DropRepositoryRequest.h>
#include <package/RefreshRepositoryRequest.h>
#include <package/PackageRoster.h>
#include <package/RepositoryConfig.h>
#include "constants.h"
#define DEBUGTASK 0
#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "TaskLooper"
static const BString kLogResultIndicator = "***";
static const BString kCompletedText =
B_TRANSLATE_COMMENT("Completed", "Completed task status message");
static const BString kFailedText =
B_TRANSLATE_COMMENT("Failed", "Failed task status message");
static const BString kAbortedText =
B_TRANSLATE_COMMENT("Aborted", "Aborted task status message");
static const BString kDescriptionText =
B_TRANSLATE_COMMENT("Description", "Failed task error description");
static const BString kDetailsText =
B_TRANSLATE_COMMENT("Details", "Job log details header");
using BSupportKit::BJob;
void
JobStateListener::JobStarted(BJob* job)
{
fJobLog.Add(job->Title());
}
void
JobStateListener::JobSucceeded(BJob* job)
{
BString resultText(kLogResultIndicator);
fJobLog.Add(resultText.Append(kCompletedText));
}
void
JobStateListener::JobFailed(BJob* job)
{
BString resultText(kLogResultIndicator);
resultText.Append(kFailedText).Append(": ")
.Append(strerror(job->Result()));
fJobLog.Add(resultText);
if (job->ErrorString().Length() > 0) {
resultText.SetTo(kLogResultIndicator);
resultText.Append(kDescriptionText).Append(": ")
.Append(job->ErrorString());
fJobLog.Add(resultText);
}
}
void
JobStateListener::JobAborted(BJob* job)
{
BString resultText(kLogResultIndicator);
resultText.Append(kAbortedText).Append(": ")
.Append(strerror(job->Result()));
fJobLog.Add(resultText);
if (job->ErrorString().Length() > 0) {
resultText.SetTo(kLogResultIndicator);
resultText.Append(kDescriptionText).Append(": ")
.Append(job->ErrorString());
fJobLog.Add(resultText);
}
}
BString
JobStateListener::GetJobLog()
{
return fJobLog.Join("\n");
}
TaskLooper::TaskLooper(const BMessenger& target)
:
BLooper("TaskLooper"),
fReplyTarget(target)
{
Run();
fMessenger.SetTo(this);
}
bool
TaskLooper::QuitRequested()
{
return MessageQueue()->IsEmpty();
}
void
TaskLooper::MessageReceived(BMessage* message)
{
switch (message->what)
{
case DO_TASK: {
RepoRow* rowItem;
status_t result = message->FindPointer(key_rowptr, (void**)&rowItem);
if (result == B_OK) {
// Check to make sure there isn't already an existing task for this
int16 queueCount = fTaskQueue.CountItems();
for (int16 index = 0; index<queueCount; index++) {
Task* task = fTaskQueue.ItemAt(index);
if (rowItem == task->rowItem)
break;
}
// Initialize task
Task* newTask = new Task();
newTask->rowItem = rowItem;
newTask->name = rowItem->Name();
newTask->resultName = newTask->name;
if (rowItem->IsEnabled()) {
newTask->taskType = DISABLE_REPO;
newTask->taskParam = newTask->name;
} else {
newTask->taskType = ENABLE_REPO;
newTask->taskParam = rowItem->Url();
}
newTask->owner = this;
newTask->fTimer = NULL;
// Add to queue and start
fTaskQueue.AddItem(newTask);
BString threadName(newTask->taskType == ENABLE_REPO ?
"enable_task" : "disable_task");
newTask->threadId = spawn_thread(_DoTask, threadName.String(),
B_NORMAL_PRIORITY, (void*)newTask);
status_t threadResult;
if (newTask->threadId < B_OK)
threadResult = B_ERROR;
else {
threadResult = resume_thread(newTask->threadId);
if (threadResult == B_OK) {
newTask->fTimer = new TaskTimer(fMessenger, newTask);
newTask->fTimer->Start(newTask->name);
// Reply to view
BMessage reply(*message);
reply.what = TASK_STARTED;
reply.AddInt16(key_count, fTaskQueue.CountItems());
fReplyTarget.SendMessage(&reply);
} else
kill_thread(newTask->threadId);
}
if (threadResult != B_OK) {
_RemoveAndDelete(newTask);
}
}
break;
}
case TASK_COMPLETED:
case TASK_COMPLETED_WITH_ERRORS:
case TASK_CANCELED: {
Task* task;
status_t result = message->FindPointer(key_taskptr, (void**)&task);
if (result == B_OK && fTaskQueue.HasItem(task)) {
task->fTimer->Stop(task->resultName);
BMessage reply(message->what);
reply.AddInt16(key_count, fTaskQueue.CountItems()-1);
reply.AddPointer(key_rowptr, task->rowItem);
if (message->what == TASK_COMPLETED_WITH_ERRORS)
reply.AddString(key_details, task->resultErrorDetails);
if (task->taskType == ENABLE_REPO
&& task->name.Compare(task->resultName) != 0)
reply.AddString(key_name, task->resultName);
fReplyTarget.SendMessage(&reply);
_RemoveAndDelete(task);
}
break;
}
case TASK_KILL_REQUEST: {
Task* task;
status_t result = message->FindPointer(key_taskptr, (void**)&task);
if (result == B_OK && fTaskQueue.HasItem(task)) {
kill_thread(task->threadId);
BMessage reply(TASK_CANCELED);
reply.AddInt16(key_count, fTaskQueue.CountItems()-1);
reply.AddPointer(key_rowptr, task->rowItem);
fReplyTarget.SendMessage(&reply);
_RemoveAndDelete(task);
}
break;
}
}
}
void
TaskLooper::_RemoveAndDelete(Task* task)
{
fTaskQueue.RemoveItem(task);
if (task->fTimer) {
task->fTimer->Lock();
task->fTimer->Quit();
task->fTimer = NULL;
}
delete task;
}
status_t
TaskLooper::_DoTask(void* data)
{
Task* task = (Task*)data;
BString errorDetails, repoName("");
status_t returnResult = B_OK;
DecisionProvider decisionProvider;
JobStateListener listener;
switch (task->taskType)
{
case DISABLE_REPO: {
BString nameParam(task->taskParam);
BPackageKit::BContext context(decisionProvider, listener);
BPackageKit::DropRepositoryRequest dropRequest(context, nameParam);
status_t result = dropRequest.Process();
if (result != B_OK) {
returnResult = result;
if (result != B_CANCELED) {
errorDetails.Append(B_TRANSLATE_COMMENT("There was an "
"error disabling the repository %name%",
"Error message, do not translate %name%"));
BString nameString("\"");
nameString.Append(nameParam).Append("\"");
errorDetails.ReplaceFirst("%name%", nameString);
_AppendErrorDetails(errorDetails, &listener);
}
}
break;
}
case ENABLE_REPO: {
BString urlParam(task->taskParam);
BPackageKit::BContext context(decisionProvider, listener);
// Add repository
bool asUserRepository = false;
// TODO does this ever change?
BPackageKit::AddRepositoryRequest addRequest(context, urlParam,
asUserRepository);
status_t result = addRequest.Process();
if (result != B_OK) {
returnResult = result;
if (result != B_CANCELED) {
errorDetails.Append(B_TRANSLATE_COMMENT("There was an "
"error enabling the repository %url%",
"Error message, do not translate %url%"));
errorDetails.ReplaceFirst("%url%", urlParam);
_AppendErrorDetails(errorDetails, &listener);
}
break;
}
// Continue on to refresh repo cache
repoName = addRequest.RepositoryName();
BPackageKit::BPackageRoster roster;
BPackageKit::BRepositoryConfig repoConfig;
roster.GetRepositoryConfig(repoName, &repoConfig);
BPackageKit::BRefreshRepositoryRequest refreshRequest(context,
repoConfig);
result = refreshRequest.Process();
if (result != B_OK) {
returnResult = result;
if (result != B_CANCELED) {
errorDetails.Append(B_TRANSLATE_COMMENT("There was an "
"error refreshing the repository cache for %name%",
"Error message, do not translate %name%"));
BString nameString("\"");
nameString.Append(repoName).Append("\"");
errorDetails.ReplaceFirst("%name%", nameString);
_AppendErrorDetails(errorDetails, &listener);
}
}
break;
}
}
// Report completion status
BMessage reply;
if (returnResult == B_OK) {
reply.what = TASK_COMPLETED;
// Add the repo name if we need to update the list row value
if (task->taskType == ENABLE_REPO)
task->resultName = repoName;
} else if (returnResult == B_CANCELED)
reply.what = TASK_CANCELED;
else {
reply.what = TASK_COMPLETED_WITH_ERRORS;
task->resultErrorDetails = errorDetails;
if (task->taskType == ENABLE_REPO)
task->resultName = repoName;
}
reply.AddPointer(key_taskptr, task);
task->owner->PostMessage(&reply);
#if DEBUGTASK
if (returnResult == B_OK || returnResult == B_CANCELED) {
BString degubDetails("Debug info:\n");
degubDetails.Append(listener.GetJobLog());
(new BAlert("debug", degubDetails, "OK"))->Go(NULL);
}
#endif // DEBUGTASK
return 0;
}
void
TaskLooper::_AppendErrorDetails(BString& details, JobStateListener* listener)
{
details.Append("\n\n").Append(kDetailsText).Append(":\n");
details.Append(listener->GetJobLog());
}
+67
View File
@@ -0,0 +1,67 @@
/*
* Copyright 2017 Haiku Inc. All rights reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
* Brian Hill
*/
#ifndef TASK_LOOPER_H
#define TASK_LOOPER_H
#include <Job.h>
#include <Looper.h>
#include <ObjectList.h>
#include <String.h>
#include <StringList.h>
#include <package/Context.h>
#include "TaskTimer.h"
class DecisionProvider : public BPackageKit::BDecisionProvider {
public:
DecisionProvider() {}
virtual bool YesNoDecisionNeeded(const BString& description,
const BString& question,
const BString& yes,
const BString& no,
const BString& defaultChoice)
{ return true; }
};
class JobStateListener : public BSupportKit::BJobStateListener {
public:
JobStateListener() {}
virtual void JobStarted(BSupportKit::BJob* job);
virtual void JobSucceeded(BSupportKit::BJob* job);
virtual void JobFailed(BSupportKit::BJob* job);
virtual void JobAborted(BSupportKit::BJob* job);
BString GetJobLog();
private:
BStringList fJobLog;
};
class TaskLooper : public BLooper {
public:
TaskLooper(const BMessenger& target);
virtual bool QuitRequested();
virtual void MessageReceived(BMessage*);
private:
BObjectList<Task> fTaskQueue;
void _RemoveAndDelete(Task* task);
static status_t _DoTask(void* data);
static void _AppendErrorDetails(BString& details,
JobStateListener* listener);
BMessenger fReplyTarget;
BMessenger fMessenger;
};
#endif
+174
View File
@@ -0,0 +1,174 @@
/*
* Copyright 2017 Haiku Inc. All rights reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
* Brian Hill
*/
#include "TaskTimer.h"
#include <Application.h>
#include <Catalog.h>
#include "constants.h"
#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "TaskTimer"
static int32 sAlertStackCount = 0;
TaskTimer::TaskTimer(const BMessenger& target, Task* owner)
:
BLooper(),
fTimeoutMicroSeconds(kTimerTimeoutSeconds * 1000000),
fTimerIsRunning(false),
fReplyTarget(target),
fMessageRunner(NULL),
fTimeoutMessage(TASK_TIMEOUT),
fTimeoutAlert(NULL),
fOwner(owner)
{
Run();
// Messenger for the Message Runner to use to send its message to the timer
fMessenger.SetTo(this);
// Invoker for the Alerts to use to send their messages to the timer
fTimeoutAlertInvoker.SetMessage(
new BMessage(TIMEOUT_ALERT_BUTTON_SELECTION));
fTimeoutAlertInvoker.SetTarget(this);
}
TaskTimer::~TaskTimer()
{
if (fTimeoutAlert) {
fTimeoutAlert->Lock();
fTimeoutAlert->Quit();
}
if (fMessageRunner)
fMessageRunner->SetCount(0);
}
bool
TaskTimer::QuitRequested()
{
return true;
}
void
TaskTimer::MessageReceived(BMessage* message)
{
switch (message->what)
{
case TASK_TIMEOUT: {
fMessageRunner = NULL;
if (fTimerIsRunning) {
BString text(B_TRANSLATE_COMMENT("The task for repository"
" %name% is taking a long time to complete.",
"Alert message. Do not translate %name%"));
BString nameString("\"");
nameString.Append(fRepositoryName).Append("\"");
text.ReplaceFirst("%name%", nameString);
fTimeoutAlert = new BAlert("timeout", text,
B_TRANSLATE_COMMENT("Keep trying", "Button label"),
B_TRANSLATE_COMMENT("Cancel task", "Button label"),
NULL, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
fTimeoutAlert->SetShortcut(0, B_ESCAPE);
// Calculate the position to correctly stack this alert
BRect windowFrame = be_app->WindowAt(0)->Frame();
int32 stackPos = _NextAlertStackCount();
float xPos = windowFrame.left
+ windowFrame.Width()/2 + stackPos * kTimerAlertOffset;
float yPos = windowFrame.top
+ (stackPos + 1) * kTimerAlertOffset;
fTimeoutAlert->Go(&fTimeoutAlertInvoker);
xPos -= fTimeoutAlert->Frame().Width()/2;
// The correct frame for the alert is not available until
// after Go is called
fTimeoutAlert->MoveTo(xPos, yPos);
}
break;
}
case TIMEOUT_ALERT_BUTTON_SELECTION: {
fTimeoutAlert = NULL;
// Timeout alert was invoked by user and timer still has not
// been stopped
if (fTimerIsRunning) {
// find which button was pressed
int32 selection = -1;
message->FindInt32("which", &selection);
if (selection == 1) {
BMessage reply(TASK_KILL_REQUEST);
reply.AddPointer(key_taskptr, fOwner);
fReplyTarget.SendMessage(&reply);
} else if (selection == 0) {
// Create new timer
fMessageRunner = new BMessageRunner(fMessenger,
&fTimeoutMessage, kTimerRetrySeconds * 1000000, 1);
}
}
break;
}
}
}
void
TaskTimer::Start(const char* name)
{
fTimerIsRunning = true;
fRepositoryName.SetTo(name);
// Create a message runner that will send a TASK_TIMEOUT message if the
// timer is not stopped
if (fMessageRunner == NULL)
fMessageRunner = new BMessageRunner(fMessenger, &fTimeoutMessage,
fTimeoutMicroSeconds, 1);
else
fMessageRunner->SetInterval(fTimeoutMicroSeconds);
}
void
TaskTimer::Stop(const char* name)
{
fTimerIsRunning = false;
// Reset max timeout so we can reuse the runner at the next Start call
if (fMessageRunner != NULL)
fMessageRunner->SetInterval(LLONG_MAX);
// If timeout alert is showing replace it
if (fTimeoutAlert) {
// Remove current alert
BRect frame = fTimeoutAlert->Frame();
fTimeoutAlert->Quit();
fTimeoutAlert = NULL;
// Display new alert that won't send a message
BString text(B_TRANSLATE_COMMENT("Good news! The task for repository "
"%name% completed.", "Alert message. Do not translate %name%"));
BString nameString("\"");
nameString.Append(name).Append("\"");
text.ReplaceFirst("%name%", nameString);
BAlert* newAlert = new BAlert("timeout", text, kOKLabel, NULL, NULL,
B_WIDTH_AS_USUAL, B_WARNING_ALERT);
newAlert->SetShortcut(0, B_ESCAPE);
newAlert->MoveTo(frame.left, frame.top);
newAlert->Go(NULL);
}
}
int32
TaskTimer::_NextAlertStackCount()
{
if (sAlertStackCount > 9)
sAlertStackCount = 0;
return sAlertStackCount++;
}
+61
View File
@@ -0,0 +1,61 @@
/*
* Copyright 2017 Haiku Inc. All rights reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
* Brian Hill
*/
#ifndef TASKTIMER_H
#define TASKTIMER_H
#include <Alert.h>
#include <Invoker.h>
#include <Looper.h>
#include <Message.h>
#include <MessageRunner.h>
#include <Messenger.h>
#include <String.h>
#include "RepoRow.h"
class TaskTimer;
class TaskLooper;
typedef struct {
RepoRow* rowItem;
int32 taskType;
BString name, taskParam;
thread_id threadId;
TaskLooper* owner;
BString resultName, resultErrorDetails;
TaskTimer* fTimer;
} Task;
class TaskTimer : public BLooper {
public:
TaskTimer(const BMessenger& target, Task* owner);
~TaskTimer();
virtual bool QuitRequested();
virtual void MessageReceived(BMessage*);
void Start(const char* name);
void Stop(const char* name);
private:
int32 fTimeoutMicroSeconds;
bool fTimerIsRunning;
BString fRepositoryName;
BMessenger fReplyTarget;
BMessenger fMessenger;
BMessageRunner* fMessageRunner;
BMessage fTimeoutMessage;
BAlert* fTimeoutAlert;
BInvoker fTimeoutAlertInvoker;
Task* fOwner;
int32 _NextAlertStackCount();
};
#endif
+95
View File
@@ -0,0 +1,95 @@
/*
* Copyright 2017 Haiku Inc. All rights reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
* Brian Hill
*/
#ifndef REPOSITORIES_CONSTANTS_H
#define REPOSITORIES_CONSTANTS_H
#include <Catalog.h>
#include <String.h>
#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "Constants"
static const float kAddWindowOffset = 10.0;
static const int16 kTimerAlertOffset = 15;
static const int16 kTimerTimeoutSeconds = 10;
static const int16 kTimerRetrySeconds = 20;
static const BString kOKLabel = B_TRANSLATE_COMMENT("OK", "Button label");
static const BString kCancelLabel = B_TRANSLATE_COMMENT("Cancel",
"Button label");
static const BString kRemoveLabel = B_TRANSLATE_COMMENT("Remove",
"Button label");
static const BString kNewRepoDefaultName = B_TRANSLATE_COMMENT("Unknown",
"Unknown repository name");
typedef struct {
const char* name;
const char* url;
} Repository;
static const Repository kDefaultRepos[] = {
{ "Haiku", "https://packages.haiku-os.org/haiku/master/"B_HAIKU_ABI_NAME
"/current"},
{ "HaikuPorts", "https://packages.haiku-os.org/haikuports/master/repo/"
B_HAIKU_ABI_NAME"/current" }
};
// Message keys
#define key_frame "frame"
#define key_name "repo_name"
#define key_url "repo_url"
#define key_text "text"
#define key_details "details"
#define key_rowptr "row_ptr"
#define key_taskptr "task_ptr"
#define key_count "count"
#define key_ID "ID"
// Messages
enum {
ADD_REPO_WINDOW = 'BHRa',
ADD_BUTTON_PRESSED,
CANCEL_BUTTON_PRESSED,
ADD_REPO_URL,
ADD_WINDOW_CLOSED,
REMOVE_REPOS,
LIST_SELECTION_CHANGED,
ENABLE_BUTTON_PRESSED,
DISABLE_BUTTON_PRESSED,
ITEM_INVOKED,
DELETE_KEY_PRESSED,
DO_TASK,
STATUS_VIEW_COMPLETED_TIMEOUT,
TASK_STARTED,
TASK_COMPLETED,
TASK_COMPLETED_WITH_ERRORS,
TASK_CANCELED,
UPDATE_LIST,
NO_TASKS,
ENABLE_REPO,
DISABLE_REPO,
TASK_TIMEOUT,
TIMEOUT_ALERT_BUTTON_SELECTION,
TASK_KILL_REQUEST
};
// Repo row task state
enum {
STATE_NOT_IN_QUEUE = 0,
STATE_IN_QUEUE_WAITING,
STATE_IN_QUEUE_RUNNING
};
#endif