From f59ce777728cf84ccdf711491772104a0fc0dd6e Mon Sep 17 00:00:00 2001
From: Lunny Xiao <xiaolunwen@gmail.com>
Date: Wed, 18 Jan 2023 23:52:04 +0800
Subject: [PATCH] Display unreferenced packages total size in package admin
 panel (#22498)

---
 models/packages/package_blob.go    | 13 +++++++++++--
 models/packages/package_file.go    |  4 ++--
 options/locale/locale_en-US.ini    |  1 +
 routers/web/admin/packages.go      | 13 ++++++++++---
 services/packages/packages.go      |  4 ++--
 templates/admin/packages/list.tmpl |  4 +++-
 6 files changed, 29 insertions(+), 10 deletions(-)

diff --git a/models/packages/package_blob.go b/models/packages/package_blob.go
index 3b4a1ecf18..a55109af96 100644
--- a/models/packages/package_blob.go
+++ b/models/packages/package_blob.go
@@ -85,7 +85,16 @@ func DeleteBlobByID(ctx context.Context, blobID int64) error {
 }
 
 // GetTotalBlobSize returns the total blobs size in bytes
-func GetTotalBlobSize() (int64, error) {
-	return db.GetEngine(db.DefaultContext).
+func GetTotalBlobSize(ctx context.Context) (int64, error) {
+	return db.GetEngine(ctx).
+		SumInt(&PackageBlob{}, "size")
+}
+
+// GetTotalUnreferencedBlobSize returns the total size of all unreferenced blobs in bytes
+func GetTotalUnreferencedBlobSize(ctx context.Context) (int64, error) {
+	return db.GetEngine(ctx).
+		Table("package_blob").
+		Join("LEFT", "package_file", "package_file.blob_id = package_blob.id").
+		Where("package_file.id IS NULL").
 		SumInt(&PackageBlob{}, "size")
 }
diff --git a/models/packages/package_file.go b/models/packages/package_file.go
index 7f794836dc..97e7a0d407 100644
--- a/models/packages/package_file.go
+++ b/models/packages/package_file.go
@@ -199,9 +199,9 @@ func SearchFiles(ctx context.Context, opts *PackageFileSearchOptions) ([]*Packag
 	return pfs, count, err
 }
 
-// CalculateBlobSize sums up all blob sizes matching the search options.
+// CalculateFileSize sums up all blob sizes matching the search options.
 // It does NOT respect the deduplication of blobs.
-func CalculateBlobSize(ctx context.Context, opts *PackageFileSearchOptions) (int64, error) {
+func CalculateFileSize(ctx context.Context, opts *PackageFileSearchOptions) (int64, error) {
 	return db.GetEngine(ctx).
 		Table("package_file").
 		Where(opts.toConds()).
diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini
index 9d1d279b0e..43a8aeb08e 100644
--- a/options/locale/locale_en-US.ini
+++ b/options/locale/locale_en-US.ini
@@ -2645,6 +2645,7 @@ repos.size = Size
 
 packages.package_manage_panel = Package Management
 packages.total_size = Total Size: %s
+packages.unreferenced_size = Unreferenced Size: %s
 packages.owner = Owner
 packages.creator = Creator
 packages.name = Name
diff --git a/routers/web/admin/packages.go b/routers/web/admin/packages.go
index 7c6d1ed840..88fb47ca01 100644
--- a/routers/web/admin/packages.go
+++ b/routers/web/admin/packages.go
@@ -51,12 +51,18 @@ func Packages(ctx *context.Context) {
 		return
 	}
 
-	totalBlobSize, err := packages_model.GetTotalBlobSize()
+	totalBlobSize, err := packages_model.GetTotalBlobSize(ctx)
 	if err != nil {
 		ctx.ServerError("GetTotalBlobSize", err)
 		return
 	}
 
+	totalUnreferencedBlobSize, err := packages_model.GetTotalUnreferencedBlobSize(ctx)
+	if err != nil {
+		ctx.ServerError("CalculateBlobSize", err)
+		return
+	}
+
 	ctx.Data["Title"] = ctx.Tr("packages.title")
 	ctx.Data["PageIsAdmin"] = true
 	ctx.Data["PageIsAdminPackages"] = true
@@ -65,8 +71,9 @@ func Packages(ctx *context.Context) {
 	ctx.Data["AvailableTypes"] = packages_model.TypeList
 	ctx.Data["SortType"] = sort
 	ctx.Data["PackageDescriptors"] = pds
-	ctx.Data["Total"] = total
-	ctx.Data["TotalBlobSize"] = totalBlobSize
+	ctx.Data["TotalCount"] = total
+	ctx.Data["TotalBlobSize"] = totalBlobSize - totalUnreferencedBlobSize
+	ctx.Data["TotalUnreferencedBlobSize"] = totalUnreferencedBlobSize
 
 	pager := context.NewPagination(int(total), setting.UI.PackagesPagingNum, page, 5)
 	pager.AddParamString("q", query)
diff --git a/services/packages/packages.go b/services/packages/packages.go
index 49f5a2fac4..410e73c048 100644
--- a/services/packages/packages.go
+++ b/services/packages/packages.go
@@ -361,11 +361,11 @@ func checkSizeQuotaExceeded(ctx context.Context, doer, owner *user_model.User, p
 	}
 
 	if setting.Packages.LimitTotalOwnerSize > -1 {
-		totalSize, err := packages_model.CalculateBlobSize(ctx, &packages_model.PackageFileSearchOptions{
+		totalSize, err := packages_model.CalculateFileSize(ctx, &packages_model.PackageFileSearchOptions{
 			OwnerID: owner.ID,
 		})
 		if err != nil {
-			log.Error("CalculateBlobSize failed: %v", err)
+			log.Error("CalculateFileSize failed: %v", err)
 			return err
 		}
 		if totalSize+uploadSize > setting.Packages.LimitTotalOwnerSize {
diff --git a/templates/admin/packages/list.tmpl b/templates/admin/packages/list.tmpl
index c39f5fc128..4c96d2bf10 100644
--- a/templates/admin/packages/list.tmpl
+++ b/templates/admin/packages/list.tmpl
@@ -4,7 +4,9 @@
 	<div class="ui container">
 		{{template "base/alert" .}}
 		<h4 class="ui top attached header">
-			{{.locale.Tr "admin.packages.package_manage_panel"}} ({{.locale.Tr "admin.total" .Total}}, {{.locale.Tr "admin.packages.total_size" (FileSize .TotalBlobSize)}})
+			{{.locale.Tr "admin.packages.package_manage_panel"}} ({{.locale.Tr "admin.total" .TotalCount}},
+			{{.locale.Tr "admin.packages.total_size" (FileSize .TotalBlobSize)}},
+			{{.locale.Tr "admin.packages.unreferenced_size" (FileSize .TotalUnreferencedBlobSize)}})
 		</h4>
 		<div class="ui attached segment">
 			<form class="ui form ignore-dirty">