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

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