From 0d4fa3e526bf5c37495b6223a50ea38a5b951b12 Mon Sep 17 00:00:00 2001
From: Andrew Lindesay <apl@lindesay.co.nz>
Date: Sat, 23 Oct 2021 21:46:08 +1300
Subject: [PATCH] HaikuDepot: Show Publish Date

Add a column to the table to show the publish
date.  Also add text on the featured packages
view to show the publish date.  Supports
sorting.

Fixes #13006

Change-Id: I19d9bc5bf7f44b5673c2ade5d00de8fdadbe1b06
Reviewed-on: https://review.haiku-os.org/c/haiku/+/4649
Reviewed-by: humdinger <humdingerb@gmail.com>
Reviewed-by: Andrew Lindesay <apl@lindesay.co.nz>
Tested-by: Commit checker robot <no-reply+buildbot@haiku-os.org>
---
 .../userguide/en/applications/haikudepot.html |   1 +
 src/apps/haikudepot/model/PackageInfo.cpp     |  24 +++-
 src/apps/haikudepot/model/PackageInfo.h       |   7 ++
 .../server/ServerPkgDataUpdateProcess.cpp     |   3 +
 .../server/schema/dumpexportpkg.json          |  11 +-
 .../haikudepot/ui/FeaturedPackagesView.cpp    |  41 +++++--
 src/apps/haikudepot/ui/PackageListView.cpp    | 115 +++++++++++++++---
 src/apps/haikudepot/util/LocaleUtils.cpp      |  19 ++-
 src/apps/haikudepot/util/LocaleUtils.h        |   2 +
 9 files changed, 189 insertions(+), 34 deletions(-)

diff --git a/docs/userguide/en/applications/haikudepot.html b/docs/userguide/en/applications/haikudepot.html
index 494c81a552..0dec11551e 100644
--- a/docs/userguide/en/applications/haikudepot.html
+++ b/docs/userguide/en/applications/haikudepot.html
@@ -105,6 +105,7 @@
 <li><p><span class="menu">Available</span>: The package exists in that repository and can be downloaded and installed. If there are any dependencies on other packages, you'll be informed of that while installing and get the choice of downloading/installing all that's necessary.</p></li>
 <li><p><span class="menu">Pending / %</span>: <i>Pending</i> is shown for a package that is queued for download/installation. While a package is downloaded, the progress is shown as percentage.</p></li>
 </ul>
+<p>The date column shows when the server system recorded the specific version of the package.  Owing to possible delays in the publishing process, this date may not be entirely accurate.</p>
 <p>You can grab the dotted line between the packages list and the info area to vertically resize the packages list.</p>
 
 <h2><a href="#"><img src="../../images/up.png" style="border:none;float:right" alt="index" /></a>
diff --git a/src/apps/haikudepot/model/PackageInfo.cpp b/src/apps/haikudepot/model/PackageInfo.cpp
index e045cc10a2..2040bf0c6a 100644
--- a/src/apps/haikudepot/model/PackageInfo.cpp
+++ b/src/apps/haikudepot/model/PackageInfo.cpp
@@ -455,7 +455,8 @@ PackageInfo::PackageInfo()
 	fDepotName(""),
 	fViewed(false),
 	fIsCollatingChanges(false),
-	fCollatedChanges(0)
+	fCollatedChanges(0),
+	fVersionCreateTimestamp(0)
 {
 }
 
@@ -486,7 +487,8 @@ PackageInfo::PackageInfo(const BPackageInfo& info)
 	fDepotName(""),
 	fViewed(false),
 	fIsCollatingChanges(false),
-	fCollatedChanges(0)
+	fCollatedChanges(0),
+	fVersionCreateTimestamp(0)
 {
 	BString publisherURL;
 	if (info.URLList().CountStrings() > 0)
@@ -533,7 +535,8 @@ PackageInfo::PackageInfo(const BString& name,
 	fDepotName(""),
 	fViewed(false),
 	fIsCollatingChanges(false),
-	fCollatedChanges(0)
+	fCollatedChanges(0),
+	fVersionCreateTimestamp(0)
 {
 }
 
@@ -566,7 +569,8 @@ PackageInfo::PackageInfo(const PackageInfo& other)
 	fDepotName(other.fDepotName),
 	fViewed(other.fViewed),
 	fIsCollatingChanges(false),
-	fCollatedChanges(0)
+	fCollatedChanges(0),
+	fVersionCreateTimestamp(other.fVersionCreateTimestamp)
 {
 }
 
@@ -599,6 +603,7 @@ PackageInfo::operator=(const PackageInfo& other)
 	fSize = other.fSize;
 	fDepotName = other.fDepotName;
 	fViewed = other.fViewed;
+	fVersionCreateTimestamp = other.fVersionCreateTimestamp;
 
 	return *this;
 }
@@ -628,7 +633,8 @@ PackageInfo::operator==(const PackageInfo& other) const
 		&& fArchitecture == other.fArchitecture
 		&& fLocalFilePath == other.fLocalFilePath
 		&& fFileName == other.fFileName
-		&& fSize == other.fSize;
+		&& fSize == other.fSize
+		&& fVersionCreateTimestamp == other.fVersionCreateTimestamp;
 }
 
 
@@ -1007,6 +1013,13 @@ PackageInfo::SetViewed()
 }
 
 
+void
+PackageInfo::SetVersionCreateTimestamp(uint64 value)
+{
+	fVersionCreateTimestamp = value;
+}
+
+
 void
 PackageInfo::SetDepotName(const BString& depotName)
 {
@@ -1315,5 +1328,6 @@ const char* package_state_to_string(PackageState state)
 			return "PENDING";
 		default:
 			debugger("unknown package state");
+			return "???";
 	}
 }
diff --git a/src/apps/haikudepot/model/PackageInfo.h b/src/apps/haikudepot/model/PackageInfo.h
index f45fc47d99..ddaef5780a 100644
--- a/src/apps/haikudepot/model/PackageInfo.h
+++ b/src/apps/haikudepot/model/PackageInfo.h
@@ -345,6 +345,10 @@ public:
 			bool				Viewed() const
 									{ return fViewed; }
 
+			void				SetVersionCreateTimestamp(uint64 value);
+			uint64				VersionCreateTimestamp() const
+									{ return fVersionCreateTimestamp; }
+
 			void				SetDepotName(const BString& depotName);
 			const BString&		DepotName() const
 									{ return fDepotName; }
@@ -400,6 +404,9 @@ private:
 
 			bool				fIsCollatingChanges;
 			uint32				fCollatedChanges;
+
+			uint64				fVersionCreateTimestamp;
+				// milliseconds since epoc
 };
 
 
diff --git a/src/apps/haikudepot/server/ServerPkgDataUpdateProcess.cpp b/src/apps/haikudepot/server/ServerPkgDataUpdateProcess.cpp
index 4c3e471d28..ab23aa95e8 100644
--- a/src/apps/haikudepot/server/ServerPkgDataUpdateProcess.cpp
+++ b/src/apps/haikudepot/server/ServerPkgDataUpdateProcess.cpp
@@ -111,6 +111,9 @@ PackageFillingPkgListener::ConsumePackage(const PackageInfoRef& package,
 
 		if (!pkgVersion->PayloadLengthIsNull())
 			package->SetSize(pkgVersion->PayloadLength());
+
+		if (!pkgVersion->CreateTimestampIsNull())
+			package->SetVersionCreateTimestamp(pkgVersion->CreateTimestamp());
 	}
 
 	int32 countPkgCategories = pkg->CountPkgCategories();
diff --git a/src/apps/haikudepot/server/schema/dumpexportpkg.json b/src/apps/haikudepot/server/schema/dumpexportpkg.json
index 653d76fa41..7dc224df8b 100644
--- a/src/apps/haikudepot/server/schema/dumpexportpkg.json
+++ b/src/apps/haikudepot/server/schema/dumpexportpkg.json
@@ -8,6 +8,9 @@
     "name": {
       "type": "string"
     },
+    "createTimestamp": {
+      "type": "integer"
+    },
     "modifyTimestamp": {
       "type": "integer"
     },
@@ -62,6 +65,12 @@
         "type": "object",
         "javaType": "org.haiku.haikudepotserver.pkg.model.dumpexport.DumpExportPkgVersion",
         "properties": {
+          "createTimestamp": {
+            "type": "integer"
+          },
+          "modifyTimestamp": {
+            "type": "integer"
+          },
           "major": {
             "type": "string"
           },
@@ -96,4 +105,4 @@
       }
     }
   }
-}
\ No newline at end of file
+}
diff --git a/src/apps/haikudepot/ui/FeaturedPackagesView.cpp b/src/apps/haikudepot/ui/FeaturedPackagesView.cpp
index f94c7536b1..dedcc4410e 100644
--- a/src/apps/haikudepot/ui/FeaturedPackagesView.cpp
+++ b/src/apps/haikudepot/ui/FeaturedPackagesView.cpp
@@ -22,6 +22,7 @@
 
 #include "BitmapView.h"
 #include "HaikuDepotConstants.h"
+#include "LocaleUtils.h"
 #include "Logger.h"
 #include "MainWindow.h"
 #include "MarkupTextView.h"
@@ -41,8 +42,9 @@
 #define X_POSITION_RATING 350.0f
 #define X_POSITION_SUMMARY 500.0f
 #define WIDTH_RATING 100.0f
-#define Y_PROPORTION_TITLE 0.4f
-#define Y_PROPORTION_PUBLISHER 0.7f
+#define Y_PROPORTION_TITLE 0.35f
+#define Y_PROPORTION_PUBLISHER 0.60f
+#define Y_PROPORTION_CHRONOLOGICAL_DATA 0.75f
 #define PADDING 8.0f
 
 
@@ -407,6 +409,7 @@ public:
 		_DrawPackageIcon(updateRect, pkg, y, selected);
 		_DrawPackageTitle(updateRect, pkg, y, selected);
 		_DrawPackagePublisher(updateRect, pkg, y, selected);
+		_DrawPackageCronologicalInfo(updateRect, pkg, y, selected);
 		_DrawPackageRating(updateRect, pkg, y, selected);
 		_DrawPackageSummary(updateRect, pkg, y, selected);
 	}
@@ -468,8 +471,8 @@ public:
 	}
 
 
-	void _DrawPackagePublisher(BRect updateRect, PackageInfoRef pkg, float y,
-		bool selected)
+	void _DrawPackageGenericTextSlug(BRect updateRect, PackageInfoRef pkg,
+		const BString& text, float y, float yProportion, bool selected)
 	{
 		static BFont* sFont = NULL;
 
@@ -488,11 +491,33 @@ public:
 		SetFont(sFont);
 
 		float maxTextWidth = (X_POSITION_RATING - HEIGHT_PACKAGE) - PADDING;
-		BString publisherName(pkg->Publisher().Name());
-		TruncateString(&publisherName, B_TRUNCATE_END, maxTextWidth);
+		BString renderedText(text);
+		TruncateString(&renderedText, B_TRUNCATE_END, maxTextWidth);
 
-		DrawString(publisherName, BPoint(HEIGHT_PACKAGE,
-			y + (HEIGHT_PACKAGE * Y_PROPORTION_PUBLISHER)));
+		DrawString(renderedText, BPoint(HEIGHT_PACKAGE,
+			y + (HEIGHT_PACKAGE * yProportion)));
+	}
+
+
+	void _DrawPackagePublisher(BRect updateRect, PackageInfoRef pkg, float y,
+		bool selected)
+	{
+		_DrawPackageGenericTextSlug(updateRect, pkg, pkg->Publisher().Name(), y,
+			Y_PROPORTION_PUBLISHER, selected);
+	}
+
+
+	void _DrawPackageCronologicalInfo(BRect updateRect, PackageInfoRef pkg,
+		float y, bool selected)
+	{
+		BString versionCreateTimestampPresentation
+			= B_TRANSLATE("%VersionCreateDate%");
+		versionCreateTimestampPresentation.ReplaceAll("%VersionCreateDate%",
+			LocaleUtils::TimestampToDateString(
+				pkg->VersionCreateTimestamp()));
+		_DrawPackageGenericTextSlug(updateRect, pkg,
+			versionCreateTimestampPresentation, y,
+			Y_PROPORTION_CHRONOLOGICAL_DATA, selected);
 	}
 
 
diff --git a/src/apps/haikudepot/ui/PackageListView.cpp b/src/apps/haikudepot/ui/PackageListView.cpp
index b720b37dae..159e45d1da 100644
--- a/src/apps/haikudepot/ui/PackageListView.cpp
+++ b/src/apps/haikudepot/ui/PackageListView.cpp
@@ -22,6 +22,7 @@
 #include <package/hpkg/Strings.h>
 #include <Window.h>
 
+#include "LocaleUtils.h"
 #include "Logger.h"
 #include "MainWindow.h"
 #include "WorkStatusView.h"
@@ -112,6 +113,23 @@ private:
 };
 
 
+class DateField : public BStringField {
+public:
+								DateField(uint64 millisSinceEpoc);
+	virtual						~DateField();
+
+			void				SetMillisSinceEpoc(uint64 millisSinceEpoc);
+			uint64				MillisSinceEpoc() const
+									{ return fMillisSinceEpoc; }
+
+private:
+			void				_SetMillisSinceEpoc(uint64 millisSinceEpoc);
+
+private:
+			uint64				fMillisSinceEpoc;
+};
+
+
 // BColumn for PackageListView which knows how to render
 // a PackageIconAndTitleField
 class PackageColumn : public BTitledColumn {
@@ -159,6 +177,7 @@ public:
 			void				UpdateSize();
 			void				UpdateRepository();
 			void				UpdateVersion();
+			void				UpdateVersionCreateTimestamp();
 
 			PackageRow*&		NextInHash()
 									{ return fNextInHash; }
@@ -297,6 +316,47 @@ SizeField::SetSize(double size)
 }
 
 
+// #pragma mark - DateField
+
+
+DateField::DateField(uint64 millisSinceEpoc)
+	:
+	BStringField(""),
+	fMillisSinceEpoc(0)
+{
+	_SetMillisSinceEpoc(millisSinceEpoc);
+}
+
+
+DateField::~DateField()
+{
+}
+
+
+void
+DateField::SetMillisSinceEpoc(uint64 millisSinceEpoc)
+{
+	if (millisSinceEpoc == fMillisSinceEpoc)
+		return;
+	_SetMillisSinceEpoc(millisSinceEpoc);
+}
+
+
+void
+DateField::_SetMillisSinceEpoc(uint64 millisSinceEpoc)
+{
+	BString dateString;
+
+	if (millisSinceEpoc == 0)
+		dateString = B_TRANSLATE_CONTEXT("-", "no package publish");
+	else
+		dateString = LocaleUtils::TimestampToDateString(millisSinceEpoc);
+
+	fMillisSinceEpoc = millisSinceEpoc;
+	SetString(dateString.String());
+}
+
+
 // #pragma mark - PackageColumn
 
 
@@ -455,6 +515,16 @@ PackageColumn::DrawField(BField* field, BRect rect, BView* parent)
 int
 PackageColumn::CompareFields(BField* field1, BField* field2)
 {
+	DateField* dateField1 = dynamic_cast<DateField*>(field1);
+	DateField* dateField2 = dynamic_cast<DateField*>(field2);
+	if (dateField1 != NULL && dateField2 != NULL) {
+		if (dateField1->MillisSinceEpoc() > dateField2->MillisSinceEpoc())
+			return -1;
+		else if (dateField1->MillisSinceEpoc() < dateField2->MillisSinceEpoc())
+			return 1;
+		return 0;
+	}
+
 	SizeField* sizeField1 = dynamic_cast<SizeField*>(field1);
 	SizeField* sizeField2 = dynamic_cast<SizeField*>(field2);
 	if (sizeField1 != NULL && sizeField2 != NULL) {
@@ -541,6 +611,7 @@ enum {
 	kStatusColumn,
 	kRepositoryColumn,
 	kVersionColumn,
+	kVersionCreateTimestampColumn,
 };
 
 
@@ -561,23 +632,13 @@ PackageRow::PackageRow(const PackageInfoRef& packageRef,
 	// NOTE: The icon BBitmap is referenced by the fPackage member.
 	UpdateIconAndTitle();
 
-	// Rating
 	UpdateRating();
-
-	// Summary
 	UpdateSummary();
-
-	// Size
 	UpdateSize();
-
-	// Status
 	UpdateState();
-
-	// Repository
 	UpdateRepository();
-
-	// Version
 	UpdateVersion();
+	UpdateVersionCreateTimestamp();
 
 	package.AddListener(fPackageListener);
 }
@@ -595,7 +656,6 @@ PackageRow::UpdateIconAndTitle()
 {
 	if (!fPackage.IsSet())
 		return;
-
 	SetField(new PackageIconAndTitleField(
 		fPackage->Name(), fPackage->Title()), kTitleColumn);
 }
@@ -606,7 +666,6 @@ PackageRow::UpdateState()
 {
 	if (!fPackage.IsSet())
 		return;
-
 	SetField(new BStringField(package_state_to_string(fPackage)),
 		kStatusColumn);
 }
@@ -617,7 +676,6 @@ PackageRow::UpdateSummary()
 {
 	if (!fPackage.IsSet())
 		return;
-
 	SetField(new BStringField(fPackage->ShortDescription()),
 		kDescriptionColumn);
 }
@@ -638,7 +696,6 @@ PackageRow::UpdateSize()
 {
 	if (!fPackage.IsSet())
 		return;
-
 	SetField(new SizeField(fPackage->Size()), kSizeColumn);
 }
 
@@ -648,7 +705,6 @@ PackageRow::UpdateRepository()
 {
 	if (!fPackage.IsSet())
 		return;
-
 	SetField(new BStringField(fPackage->DepotName()), kRepositoryColumn);
 }
 
@@ -658,11 +714,20 @@ PackageRow::UpdateVersion()
 {
 	if (!fPackage.IsSet())
 		return;
-
 	SetField(new BStringField(fPackage->Version().ToString()), kVersionColumn);
 }
 
 
+void
+PackageRow::UpdateVersionCreateTimestamp()
+{
+	if (!fPackage.IsSet())
+		return;
+	SetField(new DateField(fPackage->VersionCreateTimestamp()),
+		kVersionCreateTimestampColumn);
+}
+
+
 // #pragma mark - ItemCountView
 
 
@@ -807,7 +872,7 @@ PackageListView::PackageListView(Model* model)
 		300 * scale, 80 * scale, 1000 * scale,
 		B_TRUNCATE_MIDDLE), kDescriptionColumn);
 	PackageColumn* sizeColumn = new PackageColumn(fModel, B_TRANSLATE("Size"),
-		spacing + StringWidth(B_TRANSLATE("9999.99 KiB")), 50 * scale,
+		spacing + StringWidth("9999.99 KiB"), 50 * scale,
 		140 * scale, B_TRUNCATE_END);
 	sizeColumn->SetAlignment(B_ALIGN_RIGHT);
 	AddColumn(sizeColumn, kSizeColumn);
@@ -821,10 +886,22 @@ PackageListView::PackageListView(Model* model)
 	SetColumnVisible(kRepositoryColumn, false);
 		// invisible by default
 
+	float widthWithPlacboVersion = spacing
+		+ StringWidth("8.2.3176-2");
+		// average sort of version length as model
 	AddColumn(new PackageColumn(fModel, B_TRANSLATE("Version"),
-		50 * scale, 50 * scale, 200 * scale,
+		widthWithPlacboVersion, widthWithPlacboVersion,
+		widthWithPlacboVersion + (50 * scale),
 		B_TRUNCATE_MIDDLE), kVersionColumn);
 
+	float widthWithPlaceboDate = spacing
+		+ StringWidth(LocaleUtils::TimestampToDateString(
+			static_cast<uint64>(1000)));
+	AddColumn(new PackageColumn(fModel, B_TRANSLATE("Date"),
+		widthWithPlaceboDate, widthWithPlaceboDate,
+		widthWithPlaceboDate + (50 * scale),
+		B_TRUNCATE_END), kVersionCreateTimestampColumn);
+
 	fItemCountView = new ItemCountView();
 	AddStatusView(fItemCountView);
 }
diff --git a/src/apps/haikudepot/util/LocaleUtils.cpp b/src/apps/haikudepot/util/LocaleUtils.cpp
index fbc0b0564c..153cd0ec27 100644
--- a/src/apps/haikudepot/util/LocaleUtils.cpp
+++ b/src/apps/haikudepot/util/LocaleUtils.cpp
@@ -8,6 +8,7 @@
 
 #include <Catalog.h>
 #include <Collator.h>
+#include <DateFormat.h>
 #include <DateTimeFormat.h>
 #include <Locale.h>
 #include <LocaleRoster.h>
@@ -54,7 +55,23 @@ LocaleUtils::TimestampToDateTimeString(uint64 millis)
 	BDateTimeFormat format;
 	BString buffer;
 	if (format.Format(buffer, millis / 1000, B_SHORT_DATE_FORMAT,
-		B_SHORT_TIME_FORMAT) != B_OK)
+			B_SHORT_TIME_FORMAT) != B_OK)
+		return "!";
+
+	return buffer;
+}
+
+
+/*static*/ BString
+LocaleUtils::TimestampToDateString(uint64 millis)
+{
+	if (millis == 0)
+		return "?";
+
+	BDateFormat format;
+	BString buffer;
+	if (format.Format(buffer, millis / 1000, B_SHORT_DATE_FORMAT)
+			!= B_OK)
 		return "!";
 
 	return buffer;
diff --git a/src/apps/haikudepot/util/LocaleUtils.h b/src/apps/haikudepot/util/LocaleUtils.h
index bcf554f688..b52c77c20f 100644
--- a/src/apps/haikudepot/util/LocaleUtils.h
+++ b/src/apps/haikudepot/util/LocaleUtils.h
@@ -16,7 +16,9 @@ class LocaleUtils {
 
 public:
 	static	BCollator*		GetSharedCollator();
+
 	static	BString			TimestampToDateTimeString(uint64 millis);
+	static	BString			TimestampToDateString(uint64 millis);
 
 	static	BString			CreateTranslatedIAmMinimumAgeSlug(int minimumAge);