aboutsummaryrefslogtreecommitdiffstats
path: root/models
diff options
context:
space:
mode:
authorUnknwon <u@gogs.io>2015-08-11 23:24:40 +0800
committerUnknwon <u@gogs.io>2015-08-11 23:24:40 +0800
commit34f6cbfc2a13295d2c8ab33f17ddbca27337b18b (patch)
tree1264ba8fa4370d05c0f5efcd81d7e28380110454 /models
parent89c2bd4a0dd85261f72565ba8395644da8129fea (diff)
downloadgitea-34f6cbfc2a13295d2c8ab33f17ddbca27337b18b.tar.gz
gitea-34f6cbfc2a13295d2c8ab33f17ddbca27337b18b.zip
finish attachments when create issue
Diffstat (limited to 'models')
-rw-r--r--models/error.go21
-rw-r--r--models/issue.go106
-rw-r--r--models/migrations/migrations.go95
3 files changed, 191 insertions, 31 deletions
diff --git a/models/error.go b/models/error.go
index 91acd9cb11..8fc0ba77fb 100644
--- a/models/error.go
+++ b/models/error.go
@@ -279,3 +279,24 @@ func IsErrMilestoneNotExist(err error) bool {
func (err ErrMilestoneNotExist) Error() string {
return fmt.Sprintf("milestone does not exist [id: %d, repo_id: %d]", err.ID, err.RepoID)
}
+
+// _____ __ __ .__ __
+// / _ \_/ |__/ |______ ____ | |__ _____ ____ _____/ |_
+// / /_\ \ __\ __\__ \ _/ ___\| | \ / \_/ __ \ / \ __\
+// / | \ | | | / __ \\ \___| Y \ Y Y \ ___/| | \ |
+// \____|__ /__| |__| (____ /\___ >___| /__|_| /\___ >___| /__|
+// \/ \/ \/ \/ \/ \/ \/
+
+type ErrAttachmentNotExist struct {
+ ID int64
+ UUID string
+}
+
+func IsErrAttachmentNotExist(err error) bool {
+ _, ok := err.(ErrAttachmentNotExist)
+ return ok
+}
+
+func (err ErrAttachmentNotExist) Error() string {
+ return fmt.Sprintf("attachment does not exist [id: %d, uuid: %s]", err.ID, err.UUID)
+}
diff --git a/models/issue.go b/models/issue.go
index 2b67b11c19..37fc125c8a 100644
--- a/models/issue.go
+++ b/models/issue.go
@@ -9,7 +9,10 @@ import (
"errors"
"fmt"
"html/template"
+ "io"
+ "mime/multipart"
"os"
+ "path"
"strconv"
"strings"
"time"
@@ -20,12 +23,12 @@ import (
"github.com/gogits/gogs/modules/base"
"github.com/gogits/gogs/modules/log"
"github.com/gogits/gogs/modules/setting"
+ gouuid "github.com/gogits/gogs/modules/uuid"
)
var (
ErrIssueNotExist = errors.New("Issue does not exist")
ErrWrongIssueCounter = errors.New("Invalid number of issues for this milestone")
- ErrAttachmentNotExist = errors.New("Attachment does not exist")
ErrAttachmentNotLinked = errors.New("Attachment does not belong to this issue")
ErrMissingIssueNumber = errors.New("No issue number specified")
)
@@ -159,7 +162,20 @@ func (i *Issue) AfterDelete() {
}
// CreateIssue creates new issue with labels for repository.
-func NewIssue(repo *Repository, issue *Issue, labelIDs []int64) (err error) {
+func NewIssue(repo *Repository, issue *Issue, labelIDs []int64, uuids []string) (err error) {
+ // Check attachments.
+ attachments := make([]*Attachment, 0, len(uuids))
+ for _, uuid := range uuids {
+ attach, err := GetAttachmentByUUID(uuid)
+ if err != nil {
+ if IsErrAttachmentNotExist(err) {
+ continue
+ }
+ return fmt.Errorf("GetAttachmentByUUID[%s]: %v", uuid, err)
+ }
+ attachments = append(attachments, attach)
+ }
+
sess := x.NewSession()
defer sessionRelease(sess)
if err = sess.Begin(); err != nil {
@@ -188,6 +204,14 @@ func NewIssue(repo *Repository, issue *Issue, labelIDs []int64) (err error) {
return err
}
+ for i := range attachments {
+ attachments[i].IssueID = issue.ID
+ // No assign value could be 0, so ignore AllCols().
+ if _, err = sess.Id(attachments[i].ID).Update(attachments[i]); err != nil {
+ return fmt.Errorf("update attachment[%d]: %v", attachments[i].ID, err)
+ }
+ }
+
// Notify watchers.
act := &Action{
ActUserID: issue.Poster.Id,
@@ -1210,49 +1234,73 @@ func (c *Comment) AfterDelete() {
}
}
+// Attachment represent a attachment of issue/comment/release.
type Attachment struct {
- Id int64
- IssueId int64
- CommentId int64
+ ID int64 `xorm:"pk autoincr"`
+ UUID string `xorm:"uuid UNIQUE"`
+ IssueID int64 `xorm:"INDEX"`
+ CommentID int64
+ ReleaseID int64 `xorm:"INDEX"`
Name string
- Path string `xorm:"TEXT"`
Created time.Time `xorm:"CREATED"`
}
-// CreateAttachment creates a new attachment inside the database and
-func CreateAttachment(issueId, commentId int64, name, path string) (*Attachment, error) {
- sess := x.NewSession()
- defer sess.Close()
+// AttachmentLocalPath returns where attachment is stored in local file system based on given UUID.
+func AttachmentLocalPath(uuid string) string {
+ return path.Join(setting.AttachmentPath, uuid[0:1], uuid[1:2], uuid)
+}
+
+// LocalPath returns where attachment is stored in local file system.
+func (attach *Attachment) LocalPath() string {
+ return AttachmentLocalPath(attach.UUID)
+}
+
+// NewAttachment creates a new attachment object.
+func NewAttachment(name string, buf []byte, file multipart.File) (_ *Attachment, err error) {
+ attach := &Attachment{
+ UUID: gouuid.NewV4().String(),
+ Name: name,
+ }
+
+ if err = os.MkdirAll(path.Dir(attach.LocalPath()), os.ModePerm); err != nil {
+ return nil, fmt.Errorf("MkdirAll: %v", err)
+ }
+ fw, err := os.Create(attach.LocalPath())
+ if err != nil {
+ return nil, fmt.Errorf("Create: %v", err)
+ }
+ defer fw.Close()
+
+ if _, err = fw.Write(buf); err != nil {
+ return nil, fmt.Errorf("Write: %v", err)
+ } else if _, err = io.Copy(fw, file); err != nil {
+ return nil, fmt.Errorf("Copy: %v", err)
+ }
+
+ sess := x.NewSession()
+ defer sessionRelease(sess)
if err := sess.Begin(); err != nil {
return nil, err
}
- a := &Attachment{IssueId: issueId, CommentId: commentId, Name: name, Path: path}
-
- if _, err := sess.Insert(a); err != nil {
- sess.Rollback()
+ if _, err := sess.Insert(attach); err != nil {
return nil, err
}
- return a, sess.Commit()
+ return attach, sess.Commit()
}
-// Attachment returns the attachment by given ID.
-func GetAttachmentById(id int64) (*Attachment, error) {
- m := &Attachment{Id: id}
-
- has, err := x.Get(m)
-
+// GetAttachmentByUUID returns attachment by given UUID.
+func GetAttachmentByUUID(uuid string) (*Attachment, error) {
+ attach := &Attachment{UUID: uuid}
+ has, err := x.Get(attach)
if err != nil {
return nil, err
+ } else if !has {
+ return nil, ErrAttachmentNotExist{0, uuid}
}
-
- if !has {
- return nil, ErrAttachmentNotExist
- }
-
- return m, nil
+ return attach, nil
}
func GetAttachmentsForIssue(issueId int64) ([]*Attachment, error) {
@@ -1285,12 +1333,12 @@ func DeleteAttachment(a *Attachment, remove bool) error {
func DeleteAttachments(attachments []*Attachment, remove bool) (int, error) {
for i, a := range attachments {
if remove {
- if err := os.Remove(a.Path); err != nil {
+ if err := os.Remove(a.LocalPath()); err != nil {
return i, err
}
}
- if _, err := x.Delete(a.Id); err != nil {
+ if _, err := x.Delete(a.ID); err != nil {
return i, err
}
}
diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go
index d9a971e057..f1d6c91af5 100644
--- a/models/migrations/migrations.go
+++ b/models/migrations/migrations.go
@@ -5,8 +5,12 @@
package migrations
import (
+ "bytes"
"encoding/json"
"fmt"
+ "io/ioutil"
+ "os"
+ "path"
"strings"
"time"
@@ -16,6 +20,7 @@ import (
"github.com/gogits/gogs/modules/log"
"github.com/gogits/gogs/modules/setting"
+ gouuid "github.com/gogits/gogs/modules/uuid"
)
const _MIN_DB_VER = 0
@@ -59,6 +64,7 @@ var migrations = []Migration{
NewMigration("fix locale file load panic", fixLocaleFileLoadPanic), // V4 -> V5:v0.6.0
NewMigration("trim action compare URL prefix", trimCommitActionAppUrlPrefix), // V5 -> V6:v0.6.3
NewMigration("generate issue-label from issue", issueToIssueLabel), // V6 -> V7:v0.6.4
+ NewMigration("refactor attachment table", attachmentRefactor), // V7 -> V8:v0.6.4
}
// Migrate database to current version
@@ -97,8 +103,11 @@ func Migrate(x *xorm.Engine) error {
}
v := currentVersion.Version
- if int(v) > len(migrations) {
- return nil
+ if int(v-_MIN_DB_VER) > len(migrations) {
+ // User downgraded Gogs.
+ currentVersion.Version = int64(len(migrations) + _MIN_DB_VER)
+ _, err = x.Id(1).Update(currentVersion)
+ return err
}
for i, m := range migrations[v-_MIN_DB_VER:] {
log.Info("Migration: %s", m.Description())
@@ -515,3 +524,85 @@ func issueToIssueLabel(x *xorm.Engine) error {
return sess.Commit()
}
+
+func attachmentRefactor(x *xorm.Engine) error {
+ type Attachment struct {
+ ID int64 `xorm:"pk autoincr"`
+ UUID string `xorm:"uuid INDEX"`
+
+ // For rename purpose.
+ Path string `xorm:"-"`
+ NewPath string `xorm:"-"`
+ }
+
+ results, err := x.Query("SELECT * FROM `attachment`")
+ if err != nil {
+ return fmt.Errorf("select attachments: %v", err)
+ }
+
+ attachments := make([]*Attachment, 0, len(results))
+ for _, attach := range results {
+ if !com.IsExist(string(attach["path"])) {
+ // If the attachment is already missing, there is no point to update it.
+ continue
+ }
+ attachments = append(attachments, &Attachment{
+ ID: com.StrTo(attach["id"]).MustInt64(),
+ UUID: gouuid.NewV4().String(),
+ Path: string(attach["path"]),
+ })
+ }
+
+ sess := x.NewSession()
+ defer sessionRelease(sess)
+ if err = sess.Begin(); err != nil {
+ return err
+ }
+
+ if err = sess.Sync2(new(Attachment)); err != nil {
+ return fmt.Errorf("Sync2: %v", err)
+ }
+
+ // Note: Roll back for rename can be a dead loop,
+ // so produces a backup file.
+ var buf bytes.Buffer
+ buf.WriteString("# old path -> new path\n")
+
+ // Update database first because this is where error happens the most often.
+ for _, attach := range attachments {
+ if _, err = sess.Id(attach.ID).Update(attach); err != nil {
+ return err
+ }
+
+ attach.NewPath = path.Join(setting.AttachmentPath, attach.UUID[0:1], attach.UUID[1:2], attach.UUID)
+ buf.WriteString(attach.Path)
+ buf.WriteString("\t")
+ buf.WriteString(attach.NewPath)
+ buf.WriteString("\n")
+ }
+
+ // Then rename attachments.
+ isSucceed := true
+ defer func() {
+ if isSucceed {
+ return
+ }
+
+ dumpPath := path.Join(setting.LogRootPath, "attachment_path.dump")
+ ioutil.WriteFile(dumpPath, buf.Bytes(), 0666)
+ fmt.Println("Fail to rename some attachments, old and new paths are saved into:", dumpPath)
+ }()
+ for _, attach := range attachments {
+ if err = os.MkdirAll(path.Dir(attach.NewPath), os.ModePerm); err != nil {
+ isSucceed = false
+ return err
+ }
+
+ if err = os.Rename(attach.Path, attach.NewPath); err != nil {
+ isSucceed = false
+ return err
+ }
+ }
+
+ return sess.Commit()
+}