HaikuDepot: notify work status from main window
[haiku.git] / src / apps / haikudepot / model / Model.cpp
blob3188b241890bce9d95ac27ae147247e32ade8769
1 /*
2 * Copyright 2013-2014, Stephan Aßmus <superstippi@gmx.de>.
3 * Copyright 2014, Axel Dörfler <axeld@pinc-software.de>.
4 * Copyright 2016-2017, Andrew Lindesay <apl@lindesay.co.nz>.
5 * All rights reserved. Distributed under the terms of the MIT License.
6 */
8 #include "Model.h"
10 #include <ctime>
11 #include <stdarg.h>
12 #include <stdio.h>
13 #include <time.h>
15 #include <Autolock.h>
16 #include <Catalog.h>
17 #include <Directory.h>
18 #include <Entry.h>
19 #include <File.h>
20 #include <KeyStore.h>
21 #include <LocaleRoster.h>
22 #include <Message.h>
23 #include <Path.h>
25 #include "Logger.h"
26 #include "StorageUtils.h"
29 #undef B_TRANSLATION_CONTEXT
30 #define B_TRANSLATION_CONTEXT "Model"
33 static const char* kHaikuDepotKeyring = "HaikuDepot";
36 PackageFilter::~PackageFilter()
41 ModelListener::~ModelListener()
46 // #pragma mark - PackageFilters
49 class AnyFilter : public PackageFilter {
50 public:
51 virtual bool AcceptsPackage(const PackageInfoRef& package) const
53 return true;
58 class DepotFilter : public PackageFilter {
59 public:
60 DepotFilter(const DepotInfo& depot)
62 fDepot(depot)
66 virtual bool AcceptsPackage(const PackageInfoRef& package) const
68 // TODO: Maybe a PackageInfo ought to know the Depot it came from?
69 // But right now the same package could theoretically be provided
70 // from different depots and the filter would work correctly.
71 // Also the PackageList could actually contain references to packages
72 // instead of the packages as objects. The equal operator is quite
73 // expensive as is.
74 return fDepot.Packages().Contains(package);
77 const BString& Depot() const
79 return fDepot.Name();
82 private:
83 DepotInfo fDepot;
87 class CategoryFilter : public PackageFilter {
88 public:
89 CategoryFilter(const BString& category)
91 fCategory(category)
95 virtual bool AcceptsPackage(const PackageInfoRef& package) const
97 if (package.Get() == NULL)
98 return false;
100 const CategoryList& categories = package->Categories();
101 for (int i = categories.CountItems() - 1; i >= 0; i--) {
102 const CategoryRef& category = categories.ItemAtFast(i);
103 if (category.Get() == NULL)
104 continue;
105 if (category->Name() == fCategory)
106 return true;
108 return false;
111 const BString& Category() const
113 return fCategory;
116 private:
117 BString fCategory;
121 class ContainedInFilter : public PackageFilter {
122 public:
123 ContainedInFilter(const PackageList& packageList)
125 fPackageList(packageList)
129 virtual bool AcceptsPackage(const PackageInfoRef& package) const
131 return fPackageList.Contains(package);
134 private:
135 const PackageList& fPackageList;
139 class ContainedInEitherFilter : public PackageFilter {
140 public:
141 ContainedInEitherFilter(const PackageList& packageListA,
142 const PackageList& packageListB)
144 fPackageListA(packageListA),
145 fPackageListB(packageListB)
149 virtual bool AcceptsPackage(const PackageInfoRef& package) const
151 return fPackageListA.Contains(package)
152 || fPackageListB.Contains(package);
155 private:
156 const PackageList& fPackageListA;
157 const PackageList& fPackageListB;
161 class NotContainedInFilter : public PackageFilter {
162 public:
163 NotContainedInFilter(const PackageList* packageList, ...)
165 va_list args;
166 va_start(args, packageList);
167 while (true) {
168 const PackageList* packageList = va_arg(args, const PackageList*);
169 if (packageList == NULL)
170 break;
171 fPackageLists.Add(packageList);
173 va_end(args);
176 virtual bool AcceptsPackage(const PackageInfoRef& package) const
178 if (package.Get() == NULL)
179 return false;
181 printf("TEST %s\n", package->Name().String());
183 for (int32 i = 0; i < fPackageLists.CountItems(); i++) {
184 if (fPackageLists.ItemAtFast(i)->Contains(package)) {
185 printf(" contained in %" B_PRId32 "\n", i);
186 return false;
189 return true;
192 private:
193 List<const PackageList*, true> fPackageLists;
197 class StateFilter : public PackageFilter {
198 public:
199 StateFilter(PackageState state)
201 fState(state)
205 virtual bool AcceptsPackage(const PackageInfoRef& package) const
207 return package->State() == NONE;
210 private:
211 PackageState fState;
215 class SearchTermsFilter : public PackageFilter {
216 public:
217 SearchTermsFilter(const BString& searchTerms)
219 // Separate the string into terms at spaces
220 int32 index = 0;
221 while (index < searchTerms.Length()) {
222 int32 nextSpace = searchTerms.FindFirst(" ", index);
223 if (nextSpace < 0)
224 nextSpace = searchTerms.Length();
225 if (nextSpace > index) {
226 BString term;
227 searchTerms.CopyInto(term, index, nextSpace - index);
228 term.ToLower();
229 fSearchTerms.Add(term);
231 index = nextSpace + 1;
235 virtual bool AcceptsPackage(const PackageInfoRef& package) const
237 if (package.Get() == NULL)
238 return false;
239 // Every search term must be found in one of the package texts
240 for (int32 i = fSearchTerms.CountItems() - 1; i >= 0; i--) {
241 const BString& term = fSearchTerms.ItemAtFast(i);
242 if (!_TextContains(package->Name(), term)
243 && !_TextContains(package->Title(), term)
244 && !_TextContains(package->Publisher().Name(), term)
245 && !_TextContains(package->ShortDescription(), term)
246 && !_TextContains(package->FullDescription(), term)) {
247 return false;
250 return true;
253 BString SearchTerms() const
255 BString searchTerms;
256 for (int32 i = 0; i < fSearchTerms.CountItems(); i++) {
257 const BString& term = fSearchTerms.ItemAtFast(i);
258 if (term.IsEmpty())
259 continue;
260 if (!searchTerms.IsEmpty())
261 searchTerms.Append(" ");
262 searchTerms.Append(term);
264 return searchTerms;
267 private:
268 bool _TextContains(BString text, const BString& string) const
270 text.ToLower();
271 int32 index = text.FindFirst(string);
272 return index >= 0;
275 private:
276 StringList fSearchTerms;
280 class IsFeaturedFilter : public PackageFilter {
281 public:
282 IsFeaturedFilter()
286 virtual bool AcceptsPackage(const PackageInfoRef& package) const
288 return package.Get() != NULL && package->IsProminent();
293 static inline bool
294 is_source_package(const PackageInfoRef& package)
296 const BString& packageName = package->Name();
297 return packageName.EndsWith("_source");
301 static inline bool
302 is_develop_package(const PackageInfoRef& package)
304 const BString& packageName = package->Name();
305 return packageName.EndsWith("_devel")
306 || packageName.EndsWith("_debuginfo");
310 // #pragma mark - Model
313 Model::Model()
315 fDepots(),
317 fCategoryAudio(new PackageCategory(
318 BitmapRef(),
319 B_TRANSLATE("Audio"), "audio"), true),
320 fCategoryBusiness(new PackageCategory(
321 BitmapRef(),
322 B_TRANSLATE("Business"), "business"), true),
323 fCategoryDevelopment(new PackageCategory(
324 BitmapRef(),
325 B_TRANSLATE("Development"), "development"), true),
326 fCategoryEducation(new PackageCategory(
327 BitmapRef(),
328 B_TRANSLATE("Education"), "education"), true),
329 fCategoryGames(new PackageCategory(
330 BitmapRef(),
331 B_TRANSLATE("Games"), "games"), true),
332 fCategoryGraphics(new PackageCategory(
333 BitmapRef(),
334 B_TRANSLATE("Graphics"), "graphics"), true),
335 fCategoryInternetAndNetwork(new PackageCategory(
336 BitmapRef(),
337 B_TRANSLATE("Internet & Network"), "internetandnetwork"), true),
338 fCategoryProductivity(new PackageCategory(
339 BitmapRef(),
340 B_TRANSLATE("Productivity"), "productivity"), true),
341 fCategoryScienceAndMathematics(new PackageCategory(
342 BitmapRef(),
343 B_TRANSLATE("Science & Mathematics"), "scienceandmathematics"), true),
344 fCategorySystemAndUtilities(new PackageCategory(
345 BitmapRef(),
346 B_TRANSLATE("System & Utilities"), "systemandutilities"), true),
347 fCategoryVideo(new PackageCategory(
348 BitmapRef(),
349 B_TRANSLATE("Video"), "video"), true),
351 fCategoryFilter(PackageFilterRef(new AnyFilter(), true)),
352 fDepotFilter(""),
353 fSearchTermsFilter(PackageFilterRef(new AnyFilter(), true)),
354 fIsFeaturedFilter(),
356 fShowFeaturedPackages(true),
357 fShowAvailablePackages(true),
358 fShowInstalledPackages(true),
359 fShowSourcePackages(false),
360 fShowDevelopPackages(false)
362 _UpdateIsFeaturedFilter();
364 // Don't forget to add new categories to this list:
365 fCategories.Add(fCategoryGames);
366 fCategories.Add(fCategoryBusiness);
367 fCategories.Add(fCategoryAudio);
368 fCategories.Add(fCategoryVideo);
369 fCategories.Add(fCategoryGraphics);
370 fCategories.Add(fCategoryEducation);
371 fCategories.Add(fCategoryProductivity);
372 fCategories.Add(fCategorySystemAndUtilities);
373 fCategories.Add(fCategoryInternetAndNetwork);
374 fCategories.Add(fCategoryDevelopment);
375 fCategories.Add(fCategoryScienceAndMathematics);
376 // TODO: The server will eventually support an API to
377 // get the defined categories and their translated names.
378 // This should then be used instead of hard-coded
379 // categories and translations in the app.
381 fPreferredLanguage = "en";
382 BLocaleRoster* localeRoster = BLocaleRoster::Default();
383 if (localeRoster != NULL) {
384 BMessage preferredLanguages;
385 if (localeRoster->GetPreferredLanguages(&preferredLanguages) == B_OK) {
386 BString language;
387 if (preferredLanguages.FindString("language", 0, &language) == B_OK)
388 language.CopyInto(fPreferredLanguage, 0, 2);
392 // TODO: Fetch this from the web-app.
393 fSupportedLanguages.Add("en");
394 fSupportedLanguages.Add("de");
395 fSupportedLanguages.Add("fr");
396 fSupportedLanguages.Add("ja");
397 fSupportedLanguages.Add("es");
398 fSupportedLanguages.Add("zh");
399 fSupportedLanguages.Add("pt");
400 fSupportedLanguages.Add("ru");
402 if (!fSupportedLanguages.Contains(fPreferredLanguage)) {
403 // Force the preferred language to one of the currently supported
404 // ones, until the web application supports all ISO language codes.
405 printf("User preferred language '%s' not currently supported, "
406 "defaulting to 'en'.", fPreferredLanguage.String());
407 fPreferredLanguage = "en";
409 fWebAppInterface.SetPreferredLanguage(fPreferredLanguage);
413 Model::~Model()
418 bool
419 Model::AddListener(const ModelListenerRef& listener)
421 return fListeners.Add(listener);
425 PackageList
426 Model::CreatePackageList() const
428 // Iterate all packages from all depots.
429 // If configured, restrict depot, filter by search terms, status, name ...
430 PackageList resultList;
432 for (int32 i = 0; i < fDepots.CountItems(); i++) {
433 const DepotInfo& depot = fDepots.ItemAtFast(i);
435 if (fDepotFilter.Length() > 0 && fDepotFilter != depot.Name())
436 continue;
438 const PackageList& packages = depot.Packages();
440 for (int32 j = 0; j < packages.CountItems(); j++) {
441 const PackageInfoRef& package = packages.ItemAtFast(j);
442 if (MatchesFilter(package))
443 resultList.Add(package);
447 return resultList;
451 bool
452 Model::MatchesFilter(const PackageInfoRef& package) const
454 return fCategoryFilter->AcceptsPackage(package)
455 && fSearchTermsFilter->AcceptsPackage(package)
456 && fIsFeaturedFilter->AcceptsPackage(package)
457 && (fShowAvailablePackages || package->State() != NONE)
458 && (fShowInstalledPackages || package->State() != ACTIVATED)
459 && (fShowSourcePackages || !is_source_package(package))
460 && (fShowDevelopPackages || !is_develop_package(package));
464 bool
465 Model::AddDepot(const DepotInfo& depot)
467 return fDepots.Add(depot);
471 bool
472 Model::HasDepot(const BString& name) const
474 return NULL != DepotForName(name);
478 const DepotInfo*
479 Model::DepotForName(const BString& name) const
481 for (int32 i = fDepots.CountItems() - 1; i >= 0; i--) {
482 if (fDepots.ItemAtFast(i).Name() == name)
483 return &fDepots.ItemAtFast(i);
485 return NULL;
489 bool
490 Model::SyncDepot(const DepotInfo& depot)
492 for (int32 i = fDepots.CountItems() - 1; i >= 0; i--) {
493 const DepotInfo& existingDepot = fDepots.ItemAtFast(i);
494 if (existingDepot.Name() == depot.Name()) {
495 DepotInfo mergedDepot(existingDepot);
496 mergedDepot.SyncPackages(depot.Packages());
497 fDepots.Replace(i, mergedDepot);
498 return true;
501 return false;
505 void
506 Model::Clear()
508 fDepots.Clear();
512 void
513 Model::SetPackageState(const PackageInfoRef& package, PackageState state)
515 switch (state) {
516 default:
517 case NONE:
518 fInstalledPackages.Remove(package);
519 fActivatedPackages.Remove(package);
520 fUninstalledPackages.Remove(package);
521 break;
522 case INSTALLED:
523 if (!fInstalledPackages.Contains(package))
524 fInstalledPackages.Add(package);
525 fActivatedPackages.Remove(package);
526 fUninstalledPackages.Remove(package);
527 break;
528 case ACTIVATED:
529 if (!fInstalledPackages.Contains(package))
530 fInstalledPackages.Add(package);
531 if (!fActivatedPackages.Contains(package))
532 fActivatedPackages.Add(package);
533 fUninstalledPackages.Remove(package);
534 break;
535 case UNINSTALLED:
536 fInstalledPackages.Remove(package);
537 fActivatedPackages.Remove(package);
538 if (!fUninstalledPackages.Contains(package))
539 fUninstalledPackages.Add(package);
540 break;
543 package->SetState(state);
547 // #pragma mark - filters
550 void
551 Model::SetCategory(const BString& category)
553 PackageFilter* filter;
555 if (category.Length() == 0)
556 filter = new AnyFilter();
557 else
558 filter = new CategoryFilter(category);
560 fCategoryFilter.SetTo(filter, true);
564 BString
565 Model::Category() const
567 CategoryFilter* filter
568 = dynamic_cast<CategoryFilter*>(fCategoryFilter.Get());
569 if (filter == NULL)
570 return "";
571 return filter->Category();
575 void
576 Model::SetDepot(const BString& depot)
578 fDepotFilter = depot;
582 BString
583 Model::Depot() const
585 return fDepotFilter;
589 void
590 Model::SetSearchTerms(const BString& searchTerms)
592 PackageFilter* filter;
594 if (searchTerms.Length() == 0)
595 filter = new AnyFilter();
596 else
597 filter = new SearchTermsFilter(searchTerms);
599 fSearchTermsFilter.SetTo(filter, true);
600 _UpdateIsFeaturedFilter();
604 BString
605 Model::SearchTerms() const
607 SearchTermsFilter* filter
608 = dynamic_cast<SearchTermsFilter*>(fSearchTermsFilter.Get());
609 if (filter == NULL)
610 return "";
611 return filter->SearchTerms();
615 void
616 Model::SetShowFeaturedPackages(bool show)
618 fShowFeaturedPackages = show;
619 _UpdateIsFeaturedFilter();
623 void
624 Model::SetShowAvailablePackages(bool show)
626 fShowAvailablePackages = show;
630 void
631 Model::SetShowInstalledPackages(bool show)
633 fShowInstalledPackages = show;
637 void
638 Model::SetShowSourcePackages(bool show)
640 fShowSourcePackages = show;
644 void
645 Model::SetShowDevelopPackages(bool show)
647 fShowDevelopPackages = show;
651 // #pragma mark - information retrieval
654 void
655 Model::PopulatePackage(const PackageInfoRef& package, uint32 flags)
657 // TODO: There should probably also be a way to "unpopulate" the
658 // package information. Maybe a cache of populated packages, so that
659 // packages loose their extra information after a certain amount of
660 // time when they have not been accessed/displayed in the UI. Otherwise
661 // HaikuDepot will consume more and more resources in the packages.
662 // Especially screen-shots will be a problem eventually.
664 BAutolock locker(&fLock);
665 bool alreadyPopulated = fPopulatedPackages.Contains(package);
666 if ((flags & POPULATE_FORCE) == 0 && alreadyPopulated)
667 return;
668 if (!alreadyPopulated)
669 fPopulatedPackages.Add(package);
672 if ((flags & POPULATE_USER_RATINGS) != 0) {
673 // Retrieve info from web-app
674 BMessage info;
676 BString packageName;
677 BString architecture;
679 BAutolock locker(&fLock);
680 packageName = package->Name();
681 architecture = package->Architecture();
684 status_t status = fWebAppInterface.RetrieveUserRatings(packageName,
685 architecture, 0, 50, info);
686 if (status == B_OK) {
687 // Parse message
688 BMessage result;
689 BMessage items;
690 if (info.FindMessage("result", &result) == B_OK
691 && result.FindMessage("items", &items) == B_OK) {
693 BAutolock locker(&fLock);
694 package->ClearUserRatings();
696 int index = 0;
697 while (true) {
698 BString name;
699 name << index++;
701 BMessage item;
702 if (items.FindMessage(name, &item) != B_OK)
703 break;
705 BString user;
706 BMessage userInfo;
707 if (item.FindMessage("user", &userInfo) != B_OK
708 || userInfo.FindString("nickname", &user) != B_OK) {
709 // Ignore, we need the user name
710 continue;
713 // Extract basic info, all items are optional
714 BString languageCode;
715 BString comment;
716 double rating;
717 item.FindString("naturalLanguageCode", &languageCode);
718 item.FindString("comment", &comment);
719 if (item.FindDouble("rating", &rating) != B_OK)
720 rating = -1;
721 if (comment.Length() == 0 && rating == -1) {
722 // No useful information given.
723 continue;
726 // For which version of the package was the rating?
727 BString major = "?";
728 BString minor = "?";
729 BString micro = "";
730 BMessage version;
731 if (item.FindMessage("pkgVersion", &version) == B_OK) {
732 version.FindString("major", &major);
733 version.FindString("minor", &minor);
734 version.FindString("micro", &micro);
736 BString versionString = major;
737 versionString << ".";
738 versionString << minor;
739 if (micro.Length() > 0) {
740 versionString << ".";
741 versionString << micro;
743 // Add the rating to the PackageInfo
744 package->AddUserRating(
745 UserRating(UserInfo(user), rating,
746 comment, languageCode, versionString, 0, 0)
749 } else if (info.FindMessage("error", &result) == B_OK) {
750 result.PrintToStream();
755 if ((flags & POPULATE_SCREEN_SHOTS) != 0) {
756 ScreenshotInfoList screenshotInfos;
758 BAutolock locker(&fLock);
759 screenshotInfos = package->ScreenshotInfos();
760 package->ClearScreenshots();
762 for (int i = 0; i < screenshotInfos.CountItems(); i++) {
763 const ScreenshotInfo& info = screenshotInfos.ItemAtFast(i);
764 _PopulatePackageScreenshot(package, info, 320, false);
770 void
771 Model::SetUsername(BString username)
773 BString password;
774 if (username.Length() > 0) {
775 BPasswordKey key;
776 BKeyStore keyStore;
777 if (keyStore.GetKey(kHaikuDepotKeyring, B_KEY_TYPE_PASSWORD, username,
778 key) == B_OK) {
779 password = key.Password();
780 } else {
781 username = "";
784 SetAuthorization(username, password, false);
788 const BString&
789 Model::Username() const
791 return fWebAppInterface.Username();
795 void
796 Model::SetAuthorization(const BString& username, const BString& password,
797 bool storePassword)
799 if (storePassword && username.Length() > 0 && password.Length() > 0) {
800 BPasswordKey key(password, B_KEY_PURPOSE_WEB, username);
801 BKeyStore keyStore;
802 keyStore.AddKeyring(kHaikuDepotKeyring);
803 keyStore.AddKey(kHaikuDepotKeyring, key);
806 BAutolock locker(&fLock);
807 fWebAppInterface.SetAuthorization(username, password);
809 _NotifyAuthorizationChanged();
813 /*! When bulk repository data comes down from the server, it will
814 arrive as a json.gz payload. This is stored locally as a cache
815 and this method will provide the on-disk storage location for
816 this file.
819 status_t
820 Model::DumpExportRepositoryDataPath(BPath& path) const
822 BPath repoDataPath;
824 if (find_directory(B_USER_CACHE_DIRECTORY, &repoDataPath) == B_OK
825 && repoDataPath.Append("HaikuDepot") == B_OK
826 && create_directory(repoDataPath.Path(), 0777) == B_OK
827 && repoDataPath.Append("repository-all_en.json.gz") == B_OK) {
828 path.SetTo(repoDataPath.Path());
829 return B_OK;
832 path.Unset();
833 fprintf(stdout, "unable to find the user cache file for repositories'"
834 " data");
835 return B_ERROR;
839 status_t
840 Model::IconStoragePath(BPath& path) const
842 BPath iconStoragePath;
844 if (find_directory(B_USER_CACHE_DIRECTORY, &iconStoragePath) == B_OK
845 && iconStoragePath.Append("HaikuDepot") == B_OK
846 && iconStoragePath.Append("__allicons") == B_OK
847 && create_directory(iconStoragePath.Path(), 0777) == B_OK) {
848 path.SetTo(iconStoragePath.Path());
849 return B_OK;
852 path.Unset();
853 fprintf(stdout, "unable to find the user cache directory for icons");
854 return B_ERROR;
858 status_t
859 Model::DumpExportPkgDataPath(BPath& path,
860 const BString& repositorySourceCode) const
862 BPath repoDataPath;
863 BString leafName;
865 leafName.SetToFormat("pkg-all-%s-%s.json.gz", repositorySourceCode.String(),
866 fPreferredLanguage.String());
868 if (find_directory(B_USER_CACHE_DIRECTORY, &repoDataPath) == B_OK
869 && repoDataPath.Append("HaikuDepot") == B_OK
870 && create_directory(repoDataPath.Path(), 0777) == B_OK
871 && repoDataPath.Append(leafName.String()) == B_OK) {
872 path.SetTo(repoDataPath.Path());
873 return B_OK;
876 path.Unset();
877 fprintf(stdout, "unable to find the user cache file for pkgs' data");
878 return B_ERROR;
882 void
883 Model::_UpdateIsFeaturedFilter()
885 if (fShowFeaturedPackages && SearchTerms().IsEmpty())
886 fIsFeaturedFilter = PackageFilterRef(new IsFeaturedFilter(), true);
887 else
888 fIsFeaturedFilter = PackageFilterRef(new AnyFilter(), true);
892 void
893 Model::_PopulatePackageScreenshot(const PackageInfoRef& package,
894 const ScreenshotInfo& info, int32 scaledWidth, bool fromCacheOnly)
896 // See if there is a cached screenshot
897 BFile screenshotFile;
898 BPath screenshotCachePath;
899 bool fileExists = false;
900 BString screenshotName(info.Code());
901 screenshotName << "@" << scaledWidth;
902 screenshotName << ".png";
903 time_t modifiedTime;
904 if (find_directory(B_USER_CACHE_DIRECTORY, &screenshotCachePath) == B_OK
905 && screenshotCachePath.Append("HaikuDepot/Screenshots") == B_OK
906 && create_directory(screenshotCachePath.Path(), 0777) == B_OK
907 && screenshotCachePath.Append(screenshotName) == B_OK) {
908 // Try opening the file in read-only mode, which will fail if its
909 // not a file or does not exist.
910 fileExists = screenshotFile.SetTo(screenshotCachePath.Path(),
911 B_READ_ONLY) == B_OK;
912 if (fileExists)
913 screenshotFile.GetModificationTime(&modifiedTime);
916 if (fileExists) {
917 time_t now;
918 time(&now);
919 if (fromCacheOnly || now - modifiedTime < 60 * 60) {
920 // Cache file is recent enough, just use it and return.
921 BitmapRef bitmapRef(new(std::nothrow)SharedBitmap(screenshotFile),
922 true);
923 BAutolock locker(&fLock);
924 package->AddScreenshot(bitmapRef);
925 return;
929 if (fromCacheOnly)
930 return;
932 // Retrieve screenshot from web-app
933 BMallocIO buffer;
935 int32 scaledHeight = scaledWidth * info.Height() / info.Width();
937 status_t status = fWebAppInterface.RetrieveScreenshot(info.Code(),
938 scaledWidth, scaledHeight, &buffer);
939 if (status == B_OK) {
940 BitmapRef bitmapRef(new(std::nothrow)SharedBitmap(buffer), true);
941 BAutolock locker(&fLock);
942 package->AddScreenshot(bitmapRef);
943 locker.Unlock();
944 if (screenshotFile.SetTo(screenshotCachePath.Path(),
945 B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE) == B_OK) {
946 screenshotFile.Write(buffer.Buffer(), buffer.BufferLength());
948 } else {
949 fprintf(stderr, "Failed to retrieve screenshot for code '%s' "
950 "at %" B_PRIi32 "x%" B_PRIi32 ".\n", info.Code().String(),
951 scaledWidth, scaledHeight);
956 // #pragma mark - listener notification methods
959 void
960 Model::_NotifyAuthorizationChanged()
962 for (int32 i = fListeners.CountItems() - 1; i >= 0; i--) {
963 const ModelListenerRef& listener = fListeners.ItemAtFast(i);
964 if (listener.Get() != NULL)
965 listener->AuthorizationChanged();
970 // temporary - should not be required once the repo info url is used.
971 static void
972 normalize_repository_base_url(BUrl& url)
974 if (url.Protocol() == "https")
975 url.SetProtocol("http");
977 BString path(url.Path());
979 if (path.EndsWith("/"))
980 url.SetPath(path.Truncate(path.Length() - 1));
984 void
985 Model::ForAllDepots(void (*func)(const DepotInfo& depot, void* context),
986 void* context)
988 for (int32 i = 0; i < fDepots.CountItems(); i++) {
989 DepotInfo depotInfo = fDepots.ItemAtFast(i);
990 func(depotInfo, context);
995 // TODO; should use the repo.info url and not the base url.
997 void
998 Model::ReplaceDepotByUrl(const BString& url,
999 DepotMapper* depotMapper, void* context)
1001 for (int32 i = 0; i < fDepots.CountItems(); i++) {
1002 DepotInfo depotInfo = fDepots.ItemAtFast(i);
1004 BUrl url(url);
1005 BUrl depotUrlNormalized(depotInfo.BaseURL());
1007 normalize_repository_base_url(url);
1008 normalize_repository_base_url(depotUrlNormalized);
1010 if (url == depotUrlNormalized) {
1011 BAutolock locker(&fLock);
1012 fDepots.Replace(i, depotMapper->MapDepot(depotInfo, context));
1018 void
1019 Model::ForAllPackages(PackageConsumer* packageConsumer, void* context)
1021 for (int32 i = 0; i < fDepots.CountItems(); i++) {
1022 DepotInfo depotInfo = fDepots.ItemAtFast(i);
1023 PackageList packages = depotInfo.Packages();
1024 for(int32 j = 0; j < packages.CountItems(); j++) {
1025 const PackageInfoRef& packageInfoRef = packages.ItemAtFast(j);
1027 if (packageInfoRef != NULL) {
1028 BAutolock locker(&fLock);
1029 if (!packageConsumer->ConsumePackage(packageInfoRef, context))
1030 return;
1037 void
1038 Model::ForPackageByNameInDepot(const BString& depotName,
1039 const BString& packageName, PackageConsumer* packageConsumer, void* context)
1041 int32 depotCount = fDepots.CountItems();
1043 for (int32 i = 0; i < depotCount; i++) {
1044 DepotInfo depotInfo = fDepots.ItemAtFast(i);
1046 if (depotInfo.Name() == depotName) {
1047 int32 packageIndex = depotInfo.PackageIndexByName(packageName);
1049 if (-1 != packageIndex) {
1050 PackageList packages = depotInfo.Packages();
1051 const PackageInfoRef& packageInfoRef =
1052 packages.ItemAtFast(packageIndex);
1054 BAutolock locker(&fLock);
1055 packageConsumer->ConsumePackage(packageInfoRef,
1056 context);
1059 return;
1065 void
1066 Model::LogDepotsWithNoWebAppRepositoryCode() const
1068 int32 i;
1070 for (i = 0; i < fDepots.CountItems(); i++) {
1071 const DepotInfo& depot = fDepots.ItemAt(i);
1073 if (depot.WebAppRepositoryCode().Length() == 0) {
1074 printf("depot [%s]", depot.Name().String());
1076 if (depot.BaseURL().Length() > 0)
1077 printf(" (%s)", depot.BaseURL().String());
1079 printf(" correlates with no repository in the haiku"
1080 "depot server system\n");