summaryrefslogtreecommitdiffstats
path: root/models/migrations/v1_11/v115.go
diff options
context:
space:
mode:
Diffstat (limited to 'models/migrations/v1_11/v115.go')
-rw-r--r--models/migrations/v1_11/v115.go159
1 files changed, 159 insertions, 0 deletions
diff --git a/models/migrations/v1_11/v115.go b/models/migrations/v1_11/v115.go
new file mode 100644
index 0000000..8c631cf
--- /dev/null
+++ b/models/migrations/v1_11/v115.go
@@ -0,0 +1,159 @@
+// Copyright 2019 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_11 //nolint
+
+import (
+ "crypto/md5"
+ "fmt"
+ "io"
+ "math"
+ "os"
+ "path/filepath"
+ "time"
+
+ "code.gitea.io/gitea/modules/container"
+ "code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/util"
+
+ "xorm.io/xorm"
+)
+
+func RenameExistingUserAvatarName(x *xorm.Engine) error {
+ sess := x.NewSession()
+ defer sess.Close()
+
+ type User struct {
+ ID int64 `xorm:"pk autoincr"`
+ LowerName string `xorm:"UNIQUE NOT NULL"`
+ Avatar string
+ }
+
+ ticker := time.NewTicker(5 * time.Second)
+ defer ticker.Stop()
+
+ count, err := x.Count(new(User))
+ if err != nil {
+ return err
+ }
+ log.Info("%d User Avatar(s) to migrate ...", count)
+
+ deleteList := make(container.Set[string])
+ start := 0
+ migrated := 0
+ for {
+ if err := sess.Begin(); err != nil {
+ return fmt.Errorf("session.Begin: %w", err)
+ }
+ users := make([]*User, 0, 50)
+ if err := sess.Table("user").Asc("id").Limit(50, start).Find(&users); err != nil {
+ return fmt.Errorf("select users from id [%d]: %w", start, err)
+ }
+ if len(users) == 0 {
+ _ = sess.Rollback()
+ break
+ }
+
+ log.Info("select users [%d - %d]", start, start+len(users))
+ start += 50
+
+ for _, user := range users {
+ oldAvatar := user.Avatar
+
+ if stat, err := os.Stat(filepath.Join(setting.Avatar.Storage.Path, oldAvatar)); err != nil || !stat.Mode().IsRegular() {
+ if err == nil {
+ err = fmt.Errorf("Error: \"%s\" is not a regular file", oldAvatar)
+ }
+ log.Warn("[user: %s] os.Stat: %v", user.LowerName, err)
+ // avatar doesn't exist in the storage
+ // no need to move avatar and update database
+ // we can just skip this
+ continue
+ }
+
+ newAvatar, err := copyOldAvatarToNewLocation(user.ID, oldAvatar)
+ if err != nil {
+ _ = sess.Rollback()
+ return fmt.Errorf("[user: %s] %w", user.LowerName, err)
+ } else if newAvatar == oldAvatar {
+ continue
+ }
+
+ user.Avatar = newAvatar
+ if _, err := sess.ID(user.ID).Cols("avatar").Update(user); err != nil {
+ _ = sess.Rollback()
+ return fmt.Errorf("[user: %s] user table update: %w", user.LowerName, err)
+ }
+
+ deleteList.Add(filepath.Join(setting.Avatar.Storage.Path, oldAvatar))
+ migrated++
+ select {
+ case <-ticker.C:
+ log.Info(
+ "%d/%d (%2.0f%%) User Avatar(s) migrated (%d old avatars to be deleted) in %d batches. %d Remaining ...",
+ migrated,
+ count,
+ float64(migrated)/float64(count)*100,
+ len(deleteList),
+ int(math.Ceil(float64(migrated)/float64(50))),
+ count-int64(migrated))
+ default:
+ }
+ }
+ if err := sess.Commit(); err != nil {
+ _ = sess.Rollback()
+ return fmt.Errorf("commit session: %w", err)
+ }
+ }
+
+ deleteCount := len(deleteList)
+ log.Info("Deleting %d old avatars ...", deleteCount)
+ i := 0
+ for file := range deleteList {
+ if err := util.Remove(file); err != nil {
+ log.Warn("util.Remove: %v", err)
+ }
+ i++
+ select {
+ case <-ticker.C:
+ log.Info(
+ "%d/%d (%2.0f%%) Old User Avatar(s) deleted. %d Remaining ...",
+ i,
+ deleteCount,
+ float64(i)/float64(deleteCount)*100,
+ deleteCount-i)
+ default:
+ }
+ }
+
+ log.Info("Completed migrating %d User Avatar(s) and deleting %d Old Avatars", count, deleteCount)
+
+ return nil
+}
+
+// copyOldAvatarToNewLocation copies oldAvatar to newAvatarLocation
+// and returns newAvatar location
+func copyOldAvatarToNewLocation(userID int64, oldAvatar string) (string, error) {
+ fr, err := os.Open(filepath.Join(setting.Avatar.Storage.Path, oldAvatar))
+ if err != nil {
+ return "", fmt.Errorf("os.Open: %w", err)
+ }
+ defer fr.Close()
+
+ data, err := io.ReadAll(fr)
+ if err != nil {
+ return "", fmt.Errorf("io.ReadAll: %w", err)
+ }
+
+ newAvatar := fmt.Sprintf("%x", md5.Sum([]byte(fmt.Sprintf("%d-%x", userID, md5.Sum(data)))))
+ if newAvatar == oldAvatar {
+ return newAvatar, nil
+ }
+
+ if err := os.WriteFile(filepath.Join(setting.Avatar.Storage.Path, newAvatar), data, 0o666); err != nil {
+ return "", fmt.Errorf("os.WriteFile: %w", err)
+ }
+
+ return newAvatar, nil
+}