From 3e06490d38ee631029b020ccd408231841e5d1da Mon Sep 17 00:00:00 2001
From: Jonas Franz <info@jonasfranz.software>
Date: Sat, 31 Mar 2018 03:10:44 +0200
Subject: [PATCH] Add Size column to attachment (#3734)

* Add size column to attachment
Migrate attachments by calculating file sizes

Signed-off-by: Jonas Franz <info@jonasfranz.software>

* Calculate attachment size on creation

Signed-off-by: Jonas Franz <info@jonasfranz.software>

* Log error instead of returning error

Signed-off-by: Jonas Franz <info@jonasfranz.software>
---
 models/attachment.go             | 31 ++++++++-------------------
 models/migrations/migrations.go  |  2 ++
 models/migrations/v61.go         | 45 ++++++++++++++++++++++++++++++++++++++++
 templates/repo/release/list.tmpl |  2 +-
 4 files changed, 57 insertions(+), 23 deletions(-)
 create mode 100644 models/migrations/v61.go

diff --git a/models/attachment.go b/models/attachment.go
index 47c76990bc..4a5101b385 100644
--- a/models/attachment.go
+++ b/models/attachment.go
@@ -11,7 +11,6 @@ import (
 	"os"
 	"path"
 
-	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/setting"
 	"code.gitea.io/gitea/modules/util"
 	api "code.gitea.io/sdk/gitea"
@@ -29,6 +28,7 @@ type Attachment struct {
 	CommentID     int64
 	Name          string
 	DownloadCount int64          `xorm:"DEFAULT 0"`
+	Size          int64          `xorm:"DEFAULT 0"`
 	CreatedUnix   util.TimeStamp `xorm:"created"`
 }
 
@@ -44,13 +44,12 @@ func (a *Attachment) IncreaseDownloadCount() error {
 
 // APIFormat converts models.Attachment to api.Attachment
 func (a *Attachment) APIFormat() *api.Attachment {
-	size, _ := a.Size()
 	return &api.Attachment{
 		ID:            a.ID,
 		Name:          a.Name,
 		Created:       a.CreatedUnix.AsTime(),
 		DownloadCount: a.DownloadCount,
-		Size:          size,
+		Size:          a.Size,
 		UUID:          a.UUID,
 		DownloadURL:   a.DownloadURL(),
 	}
@@ -67,25 +66,6 @@ func (a *Attachment) LocalPath() string {
 	return AttachmentLocalPath(a.UUID)
 }
 
-// Size returns the file's size of the attachment
-func (a *Attachment) Size() (int64, error) {
-	fi, err := os.Stat(a.LocalPath())
-	if err != nil {
-		return 0, err
-	}
-	return fi.Size(), nil
-}
-
-// MustSize returns the result of a.Size() by ignoring errors
-func (a *Attachment) MustSize() int64 {
-	size, err := a.Size()
-	if err != nil {
-		log.Error(4, "size: %v", err)
-		return 0
-	}
-	return size
-}
-
 // DownloadURL returns the download url of the attached file
 func (a *Attachment) DownloadURL() string {
 	return fmt.Sprintf("%sattachments/%s", setting.AppURL, a.UUID)
@@ -115,6 +95,13 @@ func NewAttachment(name string, buf []byte, file multipart.File) (_ *Attachment,
 		return nil, fmt.Errorf("Copy: %v", err)
 	}
 
+	// Update file size
+	var fi os.FileInfo
+	if fi, err = fw.Stat(); err != nil {
+		return nil, fmt.Errorf("file size: %v", err)
+	}
+	attach.Size = fi.Size()
+
 	if _, err := x.Insert(attach); err != nil {
 		return nil, err
 	}
diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go
index 36c84a61aa..16037e1472 100644
--- a/models/migrations/migrations.go
+++ b/models/migrations/migrations.go
@@ -174,6 +174,8 @@ var migrations = []Migration{
 	NewMigration("add merge whitelist for protected branches", addProtectedBranchMergeWhitelist),
 	// v60 -> v61
 	NewMigration("add is_fsck_enabled column for repos", addFsckEnabledToRepo),
+	// v61 -> v62
+	NewMigration("add size column for attachments", addSizeToAttachment),
 }
 
 // Migrate database to current version
diff --git a/models/migrations/v61.go b/models/migrations/v61.go
new file mode 100644
index 0000000000..bcbc7553b8
--- /dev/null
+++ b/models/migrations/v61.go
@@ -0,0 +1,45 @@
+// Copyright 2018 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package migrations
+
+import (
+	"fmt"
+	"os"
+	"path"
+
+	"code.gitea.io/gitea/modules/log"
+	"code.gitea.io/gitea/modules/setting"
+
+	"github.com/go-xorm/xorm"
+)
+
+func addSizeToAttachment(x *xorm.Engine) error {
+	type Attachment struct {
+		ID   int64  `xorm:"pk autoincr"`
+		UUID string `xorm:"uuid UNIQUE"`
+		Size int64  `xorm:"DEFAULT 0"`
+	}
+	if err := x.Sync2(new(Attachment)); err != nil {
+		return fmt.Errorf("Sync2: %v", err)
+	}
+
+	attachments := make([]Attachment, 0, 100)
+	if err := x.Find(&attachments); err != nil {
+		return fmt.Errorf("query attachments: %v", err)
+	}
+	for _, attach := range attachments {
+		localPath := path.Join(setting.AttachmentPath, attach.UUID[0:1], attach.UUID[1:2], attach.UUID)
+		fi, err := os.Stat(localPath)
+		if err != nil {
+			log.Error(4, "calculate file size of attachment[UUID: %s]: %v", attach.UUID, err)
+			continue
+		}
+		attach.Size = fi.Size()
+		if _, err := x.ID(attach.ID).Cols("size").Update(attach); err != nil {
+			return fmt.Errorf("update size column: %v", err)
+		}
+	}
+	return nil
+}
diff --git a/templates/repo/release/list.tmpl b/templates/repo/release/list.tmpl
index 73e4a86821..af0f0fa82a 100644
--- a/templates/repo/release/list.tmpl
+++ b/templates/repo/release/list.tmpl
@@ -79,7 +79,7 @@
 									<li>
 										<a target="_blank" rel="noopener" href="{{AppSubUrl}}/attachments/{{.UUID}}">
 											<strong><span class="ui image octicon octicon-package" title='{{.Name}}'></span> {{.Name}}</strong>
-											<span class="ui text grey right">{{.MustSize | FileSize}}</span>
+											<span class="ui text grey right">{{.Size | FileSize}}</span>
 										</a>
 									</li>
 									{{end}}