diff options
author | Unknwon <u@gogs.io> | 2015-08-11 23:24:40 +0800 |
---|---|---|
committer | Unknwon <u@gogs.io> | 2015-08-11 23:24:40 +0800 |
commit | 34f6cbfc2a13295d2c8ab33f17ddbca27337b18b (patch) | |
tree | 1264ba8fa4370d05c0f5efcd81d7e28380110454 /models | |
parent | 89c2bd4a0dd85261f72565ba8395644da8129fea (diff) | |
download | gitea-34f6cbfc2a13295d2c8ab33f17ddbca27337b18b.tar.gz gitea-34f6cbfc2a13295d2c8ab33f17ddbca27337b18b.zip |
finish attachments when create issue
Diffstat (limited to 'models')
-rw-r--r-- | models/error.go | 21 | ||||
-rw-r--r-- | models/issue.go | 106 | ||||
-rw-r--r-- | models/migrations/migrations.go | 95 |
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() +} |