From 5bd5f161e896a560c55776fbbada27b47e0794c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stephan=20A=C3=9Fmus?= Date: Fri, 28 Mar 2014 23:46:01 +0100 Subject: [PATCH] HaikuDepot: Populate package icons from web app. * Transformed ConnectionTest into WebAppInterface * Added methods to Model to trigger the population of all packages with additional information that is needed in the list view. * This launches a thread which tries to retrieve icons for all known packages from depot.haiku-os.org. This is uncached and very slow. I guess it could even be fast enough with a change to the protocol where icons are not retrieved one at a time. --- src/apps/haikudepot/ConnectionTest.cpp | 214 ------------------------ src/apps/haikudepot/ConnectionTest.h | 30 ---- src/apps/haikudepot/Jamfile | 9 +- src/apps/haikudepot/MainWindow.cpp | 3 + src/apps/haikudepot/Model.cpp | 104 +++++++++++- src/apps/haikudepot/Model.h | 15 +- src/apps/haikudepot/WebAppInterface.cpp | 196 ++++++++++++++++++++++ src/apps/haikudepot/WebAppInterface.h | 34 ++++ 8 files changed, 351 insertions(+), 254 deletions(-) delete mode 100644 src/apps/haikudepot/ConnectionTest.cpp delete mode 100644 src/apps/haikudepot/ConnectionTest.h create mode 100644 src/apps/haikudepot/WebAppInterface.cpp create mode 100644 src/apps/haikudepot/WebAppInterface.h diff --git a/src/apps/haikudepot/ConnectionTest.cpp b/src/apps/haikudepot/ConnectionTest.cpp deleted file mode 100644 index 717d3267b6..0000000000 --- a/src/apps/haikudepot/ConnectionTest.cpp +++ /dev/null @@ -1,214 +0,0 @@ -/* - * Copyright 2014, Stephan Aßmus . - * All rights reserved. Distributed under the terms of the MIT License. - */ - -#include "ConnectionTest.h" - -#include - -#include - -#include -#include -#include -#include -#include -#include -#include - - -class ProtocolListener : public BUrlProtocolListener { -public: - ProtocolListener::ProtocolListener() - : - fDownloadIO(NULL) - { - } - - virtual void ConnectionOpened(BUrlRequest* caller) - { - printf("ConnectionOpened(%p)\n", caller); - } - - virtual void HostnameResolved(BUrlRequest* caller, const char* ip) - { - printf("HostnameResolved(%p): %s\n", caller, ip); - } - - virtual void ResponseStarted(BUrlRequest* caller) - { - printf("ResponseStarted(%p)\n", caller); - } - - virtual void HeadersReceived(BUrlRequest* caller) - { - printf("HeadersReceived(%p)\n", caller); - } - - virtual void DataReceived(BUrlRequest* caller, const char* data, - ssize_t size) - { - printf("DataReceived(%p): %ld bytes\n", caller, size); - - if (fDownloadIO != NULL) - fDownloadIO->Write(data, size); - } - - virtual void DownloadProgress(BUrlRequest* caller, ssize_t bytesReceived, - ssize_t bytesTotal) - { - printf("DownloadProgress(%p): %ld/%ld\n", caller, bytesReceived, - bytesTotal); - } - - virtual void UploadProgress(BUrlRequest* caller, ssize_t bytesSent, - ssize_t bytesTotal) - { - printf("UploadProgress(%p): %ld/%ld\n", caller, bytesSent, bytesTotal); - } - - virtual void RequestCompleted(BUrlRequest* caller, bool success) - { - printf("RequestCompleted(%p): %d\n", caller, success); - } - - virtual void DebugMessage(BUrlRequest* caller, - BUrlProtocolDebugMessage type, const char* text) - { - printf("DebugMessage(%p): %s\n", caller, text); - } - - void SetDownloadIO(BDataIO* downloadIO) - { - fDownloadIO = downloadIO; - } - -private: - BDataIO* fDownloadIO; -}; - - -ConnectionTest::ConnectionTest(const char* username, const char* password) - : - BApplication("application/x-vnd.Haiku-HaikuDepot-ConnectionTest"), - fUsername(username), - fPassword(password) -{ -} - - -ConnectionTest::~ConnectionTest() -{ -} - - -void -ConnectionTest::ReadyToRun() -{ - printf("Connecting...\n"); - - _TestDownloadIcon(); - - PostMessage(B_QUIT_REQUESTED, this); -} - - -void -ConnectionTest::_TestGetPackage() -{ - BUrl url("https://depot.haiku-os.org/api/v1/pkg"); - - ProtocolListener listener; - BUrlContext context; - BHttpHeaders headers; - - // Authentication - if (!fUsername.IsEmpty() && !fPassword.IsEmpty()) { - BString plain; - plain << fUsername << ':' << fPassword; - - char base64[1024]; - ssize_t base64Size = encode_base64(base64, plain, plain.Length(), - false); - - if (base64Size > 0 && base64Size <= (ssize_t)sizeof(base64)) { - base64[base64Size] = '\0'; - - BString authorization("Basic "); - authorization << base64; - - headers.AddHeader("Authorization", authorization); - } - } - // Content-Type - headers.AddHeader("Content-Type", "application/json"); - - BHttpRequest request(url, true, "HTTP", &listener, &context); - request.SetMethod(B_HTTP_POST); - request.SetHeaders(headers); - - BMallocIO* data = new BMallocIO(); - - BString jsonString - = "{" - "\"jsonrpc\":\"2.0\"," - "\"id\":4143431," - "\"method\":\"getPkg\"," - "\"params\":[{" - "\"name\":\"apr\"," - "\"architectureCode\":\"x86\"," - "\"versionType\":\"NONE\"" - "}]" - "}" - ; - - data->WriteAt(0, jsonString.String(), jsonString.Length()); - data->Seek(0, SEEK_SET); - request.AdoptInputData(data); - - thread_id thread = request.Run(); - wait_for_thread(thread, NULL); -} - - -void -ConnectionTest::_TestDownloadIcon() -{ - BUrl url("https://depot.haiku-os.org/pkgicon/wonderbrush_x86_gcc2.hvif"); - - BFile file("/boot/home/Desktop/wonderbrush.hvif", - B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE); - - ProtocolListener listener; - listener.SetDownloadIO(&file); - - // Content-Type - BHttpRequest request(url, true, "HTTP", &listener); - request.SetMethod(B_HTTP_GET); - - thread_id thread = request.Run(); - wait_for_thread(thread, NULL); -} - - -int -main(int argc, char* argv[]) -{ - const char* username = ""; - const char* password = ""; - - for (int i = 1; i < argc; i++) { - if (strcmp("-u", argv[i]) == 0 && i < argc - 1) - username = argv[++i]; - else if (strcmp("-p", argv[i]) == 0 && i < argc - 1) - password = argv[++i]; - } - - printf("Using username: %s, password: %s\n", username, password); - - ConnectionTest(username, password).Run(); - return 0; -} - - diff --git a/src/apps/haikudepot/ConnectionTest.h b/src/apps/haikudepot/ConnectionTest.h deleted file mode 100644 index 24c745407e..0000000000 --- a/src/apps/haikudepot/ConnectionTest.h +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2014, Stephan Aßmus . - * All rights reserved. Distributed under the terms of the MIT License. - */ -#ifndef CONNECTION_TEST_H -#define CONNECTION_TEST_H - - -#include -#include - - -class ConnectionTest : public BApplication { -public: - ConnectionTest(const char* username, - const char* password); - virtual ~ConnectionTest(); - - virtual void ReadyToRun(); - -private: - void _TestGetPackage(); - void _TestDownloadIcon(); -private: - BString fUsername; - BString fPassword; -}; - - -#endif // CONNECTION_TEST_H diff --git a/src/apps/haikudepot/Jamfile b/src/apps/haikudepot/Jamfile index 9ac8a9d8bf..0c9bbe1b98 100644 --- a/src/apps/haikudepot/Jamfile +++ b/src/apps/haikudepot/Jamfile @@ -53,6 +53,7 @@ Application HaikuDepot : PackageManager.cpp RatePackageWindow.cpp support.cpp + WebAppInterface.cpp # package_daemon ProblemWindow.cpp @@ -61,7 +62,7 @@ Application HaikuDepot : # text view stuff $(textDocumentSources) - : be package translation libcolumnlistview.a libshared.a + : be package netapi translation libcolumnlistview.a libshared.a $(TARGET_LIBSTDC++) $(TARGET_LIBSUPC++) localestub : HaikuDepot.rdef ; @@ -85,9 +86,3 @@ Application TextDocumentTest : : be translation libshared.a $(TARGET_LIBSUPC++) ; - -Application ConnectionTest : - ConnectionTest.cpp - - : be netapi translation libshared.a $(TARGET_LIBSUPC++) -; diff --git a/src/apps/haikudepot/MainWindow.cpp b/src/apps/haikudepot/MainWindow.cpp index e81df5b9b7..6bc18dffa9 100644 --- a/src/apps/haikudepot/MainWindow.cpp +++ b/src/apps/haikudepot/MainWindow.cpp @@ -580,6 +580,9 @@ MainWindow::_RefreshPackageList() fModel.AddDepot(it->second); } + // start retrieving package icons and average ratings + fModel.PopulateAllPackages(); + // compute the OS package dependencies try { // create the solver diff --git a/src/apps/haikudepot/Model.cpp b/src/apps/haikudepot/Model.cpp index 7360124580..1cee127531 100644 --- a/src/apps/haikudepot/Model.cpp +++ b/src/apps/haikudepot/Model.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2013, Stephan Aßmus . + * Copyright 2013-2014, Stephan Aßmus . * Copyright 2014, Axel Dörfler . * All rights reserved. Distributed under the terms of the MIT License. */ @@ -12,6 +12,8 @@ #include #include +#include "WebAppInterface.h" + #undef B_TRANSLATION_CONTEXT #define B_TRANSLATION_CONTEXT "Model" @@ -281,7 +283,10 @@ Model::Model() fSearchTermsFilter(PackageFilterRef(new AnyFilter(), true)), fShowSourcePackages(false), - fShowDevelopPackages(false) + fShowDevelopPackages(false), + + fPopulateAllPackagesThread(-1), + fStopPopulatingAllPackages(false) { // Don't forget to add new categories to this list: fCategories.Add(fCategoryAudio); @@ -327,6 +332,12 @@ Model::Model() } +Model::~Model() +{ + StopPopulatingAllPackages(); +} + + PackageList Model::CreatePackageList() const { @@ -368,6 +379,7 @@ Model::AddDepot(const DepotInfo& depot) void Model::Clear() { + StopPopulatingAllPackages(); fDepots.Clear(); } @@ -520,3 +532,91 @@ Model::PopulatePackage(const PackageInfoRef& package, uint32 flags) } } + + +void +Model::PopulateAllPackages() +{ + StopPopulatingAllPackages(); + + fStopPopulatingAllPackages = false; + + fPopulateAllPackagesThread = spawn_thread(&_PopulateAllPackagesEntry, + "Package populator", B_NORMAL_PRIORITY, this); + if (fPopulateAllPackagesThread >= 0) + resume_thread(fPopulateAllPackagesThread); +} + + +void +Model::StopPopulatingAllPackages() +{ + if (fPopulateAllPackagesThread < 0) + return; + + fStopPopulatingAllPackages = true; + wait_for_thread(fPopulateAllPackagesThread, NULL); + fPopulateAllPackagesThread = -1; +} + + +int32 +Model::_PopulateAllPackagesEntry(void* cookie) +{ + Model* model = static_cast(cookie); + model->_PopulateAllPackagesThread(); + return 0; +} + + +void +Model::_PopulateAllPackagesThread() +{ + int32 depotIndex = 0; + int32 packageIndex = 0; + + while (!fStopPopulatingAllPackages) { + // Obtain PackageInfoRef while keeping the depot and package lists + // locked. + PackageInfoRef package; + { + BAutolock locker(&fLock); + + if (depotIndex >= fDepots.CountItems()) + break; + const DepotInfo& depot = fDepots.ItemAt(depotIndex); + + const PackageList& packages = depot.Packages(); + if (packageIndex >= packages.CountItems()) { + // Need the next depot + packageIndex = 0; + depotIndex++; + continue; + } + + package = packages.ItemAt(packageIndex); + packageIndex++; + } + + if (package.Get() == NULL) + continue; + + _PopulatePackageIcon(package); + // TODO: Average user rating. It needs to be shown in the + // list view, so without the user clicking the package. + } +} + + +void +Model::_PopulatePackageIcon(const PackageInfoRef& package) +{ + WebAppInterface interface; + BMallocIO buffer; + + status_t status = interface.RetrievePackageIcon(package->Title(), &buffer); + if (status == B_OK) { + BitmapRef bitmapRef(new(std::nothrow)SharedBitmap(buffer), true); + package->SetIcon(bitmapRef); + } +} diff --git a/src/apps/haikudepot/Model.h b/src/apps/haikudepot/Model.h index 950a444aa9..a03b667196 100644 --- a/src/apps/haikudepot/Model.h +++ b/src/apps/haikudepot/Model.h @@ -1,5 +1,5 @@ /* - * Copyright 2013, Stephan Aßmus . + * Copyright 2013-2014, Stephan Aßmus . * All rights reserved. Distributed under the terms of the MIT License. */ #ifndef MODEL_H @@ -24,6 +24,7 @@ typedef BReference PackageFilterRef; class Model { public: Model(); + virtual ~Model(); BLocker* Lock() { return &fLock; } @@ -85,6 +86,15 @@ public: void PopulatePackage(const PackageInfoRef& package, uint32 flags); + void PopulateAllPackages(); + void StopPopulatingAllPackages(); + +private: + static int32 _PopulateAllPackagesEntry(void* cookie); + void _PopulateAllPackagesThread(); + + void _PopulatePackageIcon( + const PackageInfoRef& package); private: BLocker fLock; @@ -117,6 +127,9 @@ private: bool fShowSourcePackages; bool fShowDevelopPackages; + + thread_id fPopulateAllPackagesThread; + volatile bool fStopPopulatingAllPackages; }; diff --git a/src/apps/haikudepot/WebAppInterface.cpp b/src/apps/haikudepot/WebAppInterface.cpp new file mode 100644 index 0000000000..bf05e11146 --- /dev/null +++ b/src/apps/haikudepot/WebAppInterface.cpp @@ -0,0 +1,196 @@ +/* + * Copyright 2014, Stephan Aßmus . + * All rights reserved. Distributed under the terms of the MIT License. + */ + +#include "WebAppInterface.h" + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "PackageInfo.h" + + +class ProtocolListener : public BUrlProtocolListener { +public: + ProtocolListener::ProtocolListener() + : + fDownloadIO(NULL) + { + } + + virtual void ConnectionOpened(BUrlRequest* caller) + { +// printf("ConnectionOpened(%p)\n", caller); + } + + virtual void HostnameResolved(BUrlRequest* caller, const char* ip) + { +// printf("HostnameResolved(%p): %s\n", caller, ip); + } + + virtual void ResponseStarted(BUrlRequest* caller) + { +// printf("ResponseStarted(%p)\n", caller); + } + + virtual void HeadersReceived(BUrlRequest* caller) + { +// printf("HeadersReceived(%p)\n", caller); + } + + virtual void DataReceived(BUrlRequest* caller, const char* data, + ssize_t size) + { +// printf("DataReceived(%p): %ld bytes\n", caller, size); + + if (fDownloadIO != NULL) + fDownloadIO->Write(data, size); + } + + virtual void DownloadProgress(BUrlRequest* caller, ssize_t bytesReceived, + ssize_t bytesTotal) + { +// printf("DownloadProgress(%p): %ld/%ld\n", caller, bytesReceived, +// bytesTotal); + } + + virtual void UploadProgress(BUrlRequest* caller, ssize_t bytesSent, + ssize_t bytesTotal) + { +// printf("UploadProgress(%p): %ld/%ld\n", caller, bytesSent, bytesTotal); + } + + virtual void RequestCompleted(BUrlRequest* caller, bool success) + { +// printf("RequestCompleted(%p): %d\n", caller, success); + } + + virtual void DebugMessage(BUrlRequest* caller, + BUrlProtocolDebugMessage type, const char* text) + { +// printf("DebugMessage(%p): %s\n", caller, text); + } + + void SetDownloadIO(BDataIO* downloadIO) + { + fDownloadIO = downloadIO; + } + +private: + BDataIO* fDownloadIO; +}; + + +WebAppInterface::WebAppInterface() +{ +} + + +WebAppInterface::~WebAppInterface() +{ +} + + +void +WebAppInterface::SetAuthorization(const BString& username, + const BString& password) +{ + fUsername = username; + fPassword = password; +} + + +//void +//WebAppInterface::_TestGetPackage() +//{ +// BUrl url("https://depot.haiku-os.org/api/v1/pkg"); +// +// ProtocolListener listener; +// BUrlContext context; +// BHttpHeaders headers; +// +// // Authentication +// if (!fUsername.IsEmpty() && !fPassword.IsEmpty()) { +// BString plain; +// plain << fUsername << ':' << fPassword; +// +// char base64[1024]; +// ssize_t base64Size = encode_base64(base64, plain, plain.Length(), +// false); +// +// if (base64Size > 0 && base64Size <= (ssize_t)sizeof(base64)) { +// base64[base64Size] = '\0'; +// +// BString authorization("Basic "); +// authorization << base64; +// +// headers.AddHeader("Authorization", authorization); +// } +// } +// // Content-Type +// headers.AddHeader("Content-Type", "application/json"); +// +// BHttpRequest request(url, true, "HTTP", &listener, &context); +// request.SetMethod(B_HTTP_POST); +// request.SetHeaders(headers); +// +// BMallocIO* data = new BMallocIO(); +// +// BString jsonString +// = "{" +// "\"jsonrpc\":\"2.0\"," +// "\"id\":4143431," +// "\"method\":\"getPkg\"," +// "\"params\":[{" +// "\"name\":\"apr\"," +// "\"architectureCode\":\"x86\"," +// "\"versionType\":\"NONE\"" +// "}]" +// "}" +// ; +// +// data->WriteAt(0, jsonString.String(), jsonString.Length()); +// data->Seek(0, SEEK_SET); +// request.AdoptInputData(data); +// +// thread_id thread = request.Run(); +// wait_for_thread(thread, NULL); +//} + + +status_t +WebAppInterface::RetrievePackageIcon(const BString& packageName, + BDataIO* stream) +{ + BString urlString = "https://depot.haiku-os.org/pkgicon/"; + urlString << packageName << ".hvif"; + + BUrl url(urlString); + + ProtocolListener listener; + listener.SetDownloadIO(stream); + + BHttpRequest request(url, true, "HTTP", &listener); + request.SetMethod(B_HTTP_GET); + + thread_id thread = request.Run(); + wait_for_thread(thread, NULL); + + const BHttpResult& result = dynamic_cast( + request.Result()); + + int32 statusCode = result.StatusCode(); + + if (statusCode == 200) + return B_OK; + + return B_ERROR; +} diff --git a/src/apps/haikudepot/WebAppInterface.h b/src/apps/haikudepot/WebAppInterface.h new file mode 100644 index 0000000000..5635f47f56 --- /dev/null +++ b/src/apps/haikudepot/WebAppInterface.h @@ -0,0 +1,34 @@ +/* + * Copyright 2014, Stephan Aßmus . + * All rights reserved. Distributed under the terms of the MIT License. + */ +#ifndef WEB_APP_INTERFACE_H +#define WEB_APP_INTERFACE_H + + +#include +#include + + +class BDataIO; + + +class WebAppInterface { +public: + WebAppInterface(); + virtual ~WebAppInterface(); + + void SetAuthorization(const BString& username, + const BString& password); + + status_t RetrievePackageIcon( + const BString& packageName, + BDataIO* stream); + +private: + BString fUsername; + BString fPassword; +}; + + +#endif // WEB_APP_INTERFACE_H