You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

v115.go 4.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. // Copyright 2019 The Gitea Authors. All rights reserved.
  2. // Use of this source code is governed by a MIT-style
  3. // license that can be found in the LICENSE file.
  4. package migrations
  5. import (
  6. "crypto/md5"
  7. "fmt"
  8. "io/ioutil"
  9. "math"
  10. "os"
  11. "path/filepath"
  12. "time"
  13. "code.gitea.io/gitea/modules/log"
  14. "code.gitea.io/gitea/modules/setting"
  15. "code.gitea.io/gitea/modules/util"
  16. "xorm.io/xorm"
  17. )
  18. func renameExistingUserAvatarName(x *xorm.Engine) error {
  19. sess := x.NewSession()
  20. defer sess.Close()
  21. type User struct {
  22. ID int64 `xorm:"pk autoincr"`
  23. LowerName string `xorm:"UNIQUE NOT NULL"`
  24. Avatar string
  25. }
  26. ticker := time.NewTicker(5 * time.Second)
  27. defer ticker.Stop()
  28. count, err := x.Count(new(User))
  29. if err != nil {
  30. return err
  31. }
  32. log.Info("%d User Avatar(s) to migrate ...", count)
  33. deleteList := make(map[string]struct{})
  34. start := 0
  35. migrated := 0
  36. for {
  37. if err := sess.Begin(); err != nil {
  38. return fmt.Errorf("session.Begin: %v", err)
  39. }
  40. users := make([]*User, 0, 50)
  41. if err := sess.Table("user").Asc("id").Limit(50, start).Find(&users); err != nil {
  42. return fmt.Errorf("select users from id [%d]: %v", start, err)
  43. }
  44. if len(users) == 0 {
  45. _ = sess.Rollback()
  46. break
  47. }
  48. log.Info("select users [%d - %d]", start, start+len(users))
  49. start += 50
  50. for _, user := range users {
  51. oldAvatar := user.Avatar
  52. if stat, err := os.Stat(filepath.Join(setting.Avatar.Path, oldAvatar)); err != nil || !stat.Mode().IsRegular() {
  53. if err == nil {
  54. err = fmt.Errorf("Error: \"%s\" is not a regular file", oldAvatar)
  55. }
  56. log.Warn("[user: %s] os.Stat: %v", user.LowerName, err)
  57. // avatar doesn't exist in the storage
  58. // no need to move avatar and update database
  59. // we can just skip this
  60. continue
  61. }
  62. newAvatar, err := copyOldAvatarToNewLocation(user.ID, oldAvatar)
  63. if err != nil {
  64. _ = sess.Rollback()
  65. return fmt.Errorf("[user: %s] %v", user.LowerName, err)
  66. } else if newAvatar == oldAvatar {
  67. continue
  68. }
  69. user.Avatar = newAvatar
  70. if _, err := sess.ID(user.ID).Cols("avatar").Update(user); err != nil {
  71. _ = sess.Rollback()
  72. return fmt.Errorf("[user: %s] user table update: %v", user.LowerName, err)
  73. }
  74. deleteList[filepath.Join(setting.Avatar.Path, oldAvatar)] = struct{}{}
  75. migrated++
  76. select {
  77. case <-ticker.C:
  78. log.Info(
  79. "%d/%d (%2.0f%%) User Avatar(s) migrated (%d old avatars to be deleted) in %d batches. %d Remaining ...",
  80. migrated,
  81. count,
  82. float64(migrated)/float64(count)*100,
  83. len(deleteList),
  84. int(math.Ceil(float64(migrated)/float64(50))),
  85. count-int64(migrated))
  86. default:
  87. }
  88. }
  89. if err := sess.Commit(); err != nil {
  90. _ = sess.Rollback()
  91. return fmt.Errorf("commit session: %v", err)
  92. }
  93. }
  94. deleteCount := len(deleteList)
  95. log.Info("Deleting %d old avatars ...", deleteCount)
  96. i := 0
  97. for file := range deleteList {
  98. if err := util.Remove(file); err != nil {
  99. log.Warn("util.Remove: %v", err)
  100. }
  101. i++
  102. select {
  103. case <-ticker.C:
  104. log.Info(
  105. "%d/%d (%2.0f%%) Old User Avatar(s) deleted. %d Remaining ...",
  106. i,
  107. deleteCount,
  108. float64(i)/float64(deleteCount)*100,
  109. deleteCount-i)
  110. default:
  111. }
  112. }
  113. log.Info("Completed migrating %d User Avatar(s) and deleting %d Old Avatars", count, deleteCount)
  114. return nil
  115. }
  116. // copyOldAvatarToNewLocation copies oldAvatar to newAvatarLocation
  117. // and returns newAvatar location
  118. func copyOldAvatarToNewLocation(userID int64, oldAvatar string) (string, error) {
  119. fr, err := os.Open(filepath.Join(setting.Avatar.Path, oldAvatar))
  120. if err != nil {
  121. return "", fmt.Errorf("os.Open: %v", err)
  122. }
  123. defer fr.Close()
  124. data, err := ioutil.ReadAll(fr)
  125. if err != nil {
  126. return "", fmt.Errorf("ioutil.ReadAll: %v", err)
  127. }
  128. newAvatar := fmt.Sprintf("%x", md5.Sum([]byte(fmt.Sprintf("%d-%x", userID, md5.Sum(data)))))
  129. if newAvatar == oldAvatar {
  130. return newAvatar, nil
  131. }
  132. if err := ioutil.WriteFile(filepath.Join(setting.Avatar.Path, newAvatar), data, 0666); err != nil {
  133. return "", fmt.Errorf("ioutil.WriteFile: %v", err)
  134. }
  135. return newAvatar, nil
  136. }