From 64375d875b4d46a6081026290da8efd82c84b25f Mon Sep 17 00:00:00 2001 From: Philip Couling Date: Sun, 15 Jan 2017 14:57:00 +0000 Subject: [PATCH] Attach to release (#673) * Moved attachaments POST url from /issues/attachments to /attachments * Implemented attachment upload on release page * Implemented downloading attachments on the release page * Added zip and gzip files to default allowed attachments * Implemented uploading attachments on edit release * Renamed UploadIssueAttachment to UploadAttachment --- cmd/web.go | 4 +- conf/app.ini | 2 +- models/release.go | 102 +++++++++++++++++++++++++- modules/auth/repo_form.go | 2 + modules/setting/setting.go | 2 +- routers/api/v1/repo/release.go | 4 +- routers/repo/issue.go | 4 +- routers/repo/release.go | 23 +++++- templates/repo/issue/comment_tab.tmpl | 2 +- templates/repo/release/list.tmpl | 9 +++ templates/repo/release/new.tmpl | 4 + 11 files changed, 144 insertions(+), 14 deletions(-) diff --git a/cmd/web.go b/cmd/web.go index 2ab680ecf3b..ff4fcc376dd 100644 --- a/cmd/web.go +++ b/cmd/web.go @@ -309,7 +309,7 @@ func runWeb(ctx *cli.Context) error { return } }) - m.Post("/issues/attachments", repo.UploadIssueAttachment) + m.Post("/attachments", repo.UploadAttachment) }, ignSignIn) m.Group("/:username", func() { @@ -463,13 +463,11 @@ func runWeb(ctx *cli.Context) error { m.Get("/:id/:action", repo.ChangeMilestonStatus) m.Post("/delete", repo.DeleteMilestone) }, reqRepoWriter, context.RepoRef()) - m.Group("/releases", func() { m.Get("/new", repo.NewRelease) m.Post("/new", bindIgnErr(auth.NewReleaseForm{}), repo.NewReleasePost) m.Post("/delete", repo.DeleteRelease) }, reqRepoWriter, context.RepoRef()) - m.Group("/releases", func() { m.Get("/edit/*", repo.EditRelease) m.Post("/edit/*", bindIgnErr(auth.EditReleaseForm{}), repo.EditReleasePost) diff --git a/conf/app.ini b/conf/app.ini index f0a2b8ceed0..ec4a3e30348 100644 --- a/conf/app.ini +++ b/conf/app.ini @@ -289,7 +289,7 @@ ENABLE = true ; Path for attachments. Defaults to `data/attachments` PATH = data/attachments ; One or more allowed types, e.g. image/jpeg|image/png -ALLOWED_TYPES = image/jpeg|image/png +ALLOWED_TYPES = image/jpeg|image/png|application/zip|application/gzip ; Max size of each file. Defaults to 32MB MAX_SIZE = 4 ; Max number of files per upload. Defaults to 10 diff --git a/models/release.go b/models/release.go index 67ef81e21c7..113a0d68e4f 100644 --- a/models/release.go +++ b/models/release.go @@ -38,6 +38,8 @@ type Release struct { IsDraft bool `xorm:"NOT NULL DEFAULT false"` IsPrerelease bool + Attachments []*Attachment `xorm:"-"` + Created time.Time `xorm:"-"` CreatedUnix int64 `xorm:"INDEX"` } @@ -155,8 +157,33 @@ func createTag(gitRepo *git.Repository, rel *Release) error { return nil } +func addReleaseAttachments(releaseID int64, attachmentUUIDs []string) (err error) { + // Check attachments + var attachments = make([]*Attachment,0) + for _, uuid := range attachmentUUIDs { + attach, err := getAttachmentByUUID(x, uuid) + if err != nil { + if IsErrAttachmentNotExist(err) { + continue + } + return fmt.Errorf("getAttachmentByUUID [%s]: %v", uuid, err) + } + attachments = append(attachments, attach) + } + + for i := range attachments { + attachments[i].ReleaseID = releaseID + // No assign value could be 0, so ignore AllCols(). + if _, err = x.Id(attachments[i].ID).Update(attachments[i]); err != nil { + return fmt.Errorf("update attachment [%d]: %v", attachments[i].ID, err) + } + } + + return +} + // CreateRelease creates a new release of repository. -func CreateRelease(gitRepo *git.Repository, rel *Release) error { +func CreateRelease(gitRepo *git.Repository, rel *Release, attachmentUUIDs []string) error { isExist, err := IsReleaseExist(rel.RepoID, rel.TagName) if err != nil { return err @@ -168,7 +195,14 @@ func CreateRelease(gitRepo *git.Repository, rel *Release) error { return err } rel.LowerTagName = strings.ToLower(rel.TagName) + _, err = x.InsertOne(rel) + if err != nil { + return err + } + + err = addReleaseAttachments(rel.ID, attachmentUUIDs) + return err } @@ -222,6 +256,64 @@ func GetReleasesByRepoIDAndNames(repoID int64, tagNames []string) (rels []*Relea return rels, err } +type releaseMetaSearch struct { + ID [] int64 + Rel [] *Release +} +func (s releaseMetaSearch) Len() int { + return len(s.ID) +} +func (s releaseMetaSearch) Swap(i, j int) { + s.ID[i], s.ID[j] = s.ID[j], s.ID[i] + s.Rel[i], s.Rel[j] = s.Rel[j], s.Rel[i] +} +func (s releaseMetaSearch) Less(i, j int) bool { + return s.ID[i] < s.ID[j] +} + +// GetReleaseAttachments retrieves the attachments for releases +func GetReleaseAttachments(rels ... *Release) (err error){ + if len(rels) == 0 { + return + } + + // To keep this efficient as possible sort all releases by id, + // select attachments by release id, + // then merge join them + + // Sort + var sortedRels = releaseMetaSearch{ID: make([]int64, len(rels)), Rel: make([]*Release, len(rels))} + var attachments [] *Attachment + for index, element := range rels { + element.Attachments = []*Attachment{} + sortedRels.ID[index] = element.ID + sortedRels.Rel[index] = element + } + sort.Sort(sortedRels) + + // Select attachments + err = x. + Asc("release_id"). + In("release_id", sortedRels.ID). + Find(&attachments, Attachment{}) + + if err != nil { + return err + } + + // merge join + var currentIndex = 0 + for _, attachment := range attachments { + for sortedRels.ID[currentIndex] < attachment.ReleaseID { + currentIndex++ + } + sortedRels.Rel[currentIndex].Attachments = append(sortedRels.Rel[currentIndex].Attachments, attachment) + } + + return + +} + type releaseSorter struct { rels []*Release } @@ -249,11 +341,17 @@ func SortReleases(rels []*Release) { } // UpdateRelease updates information of a release. -func UpdateRelease(gitRepo *git.Repository, rel *Release) (err error) { +func UpdateRelease(gitRepo *git.Repository, rel *Release, attachmentUUIDs []string) (err error) { if err = createTag(gitRepo, rel); err != nil { return err } _, err = x.Id(rel.ID).AllCols().Update(rel) + if err != nil { + return err + } + + err = addReleaseAttachments(rel.ID, attachmentUUIDs) + return err } diff --git a/modules/auth/repo_form.go b/modules/auth/repo_form.go index 8a200c02060..dda1f9d2526 100644 --- a/modules/auth/repo_form.go +++ b/modules/auth/repo_form.go @@ -267,6 +267,7 @@ type NewReleaseForm struct { Content string Draft string Prerelease bool + Files []string } // Validate valideates the fields @@ -280,6 +281,7 @@ type EditReleaseForm struct { Content string `form:"content"` Draft string `form:"draft"` Prerelease bool `form:"prerelease"` + Files []string } // Validate valideates the fields diff --git a/modules/setting/setting.go b/modules/setting/setting.go index 523582f703a..ab916c1b53f 100644 --- a/modules/setting/setting.go +++ b/modules/setting/setting.go @@ -718,7 +718,7 @@ please consider changing to GITEA_CUSTOM`) if !filepath.IsAbs(AttachmentPath) { AttachmentPath = path.Join(workDir, AttachmentPath) } - AttachmentAllowedTypes = strings.Replace(sec.Key("ALLOWED_TYPES").MustString("image/jpeg,image/png"), "|", ",", -1) + AttachmentAllowedTypes = strings.Replace(sec.Key("ALLOWED_TYPES").MustString("image/jpeg,image/png,application/zip,application/gzip"), "|", ",", -1) AttachmentMaxSize = sec.Key("MAX_SIZE").MustInt64(4) AttachmentMaxFiles = sec.Key("MAX_FILES").MustInt(5) AttachmentEnabled = sec.Key("ENABLE").MustBool(true) diff --git a/routers/api/v1/repo/release.go b/routers/api/v1/repo/release.go index d14f2f3caeb..7dacb8018a8 100644 --- a/routers/api/v1/repo/release.go +++ b/routers/api/v1/repo/release.go @@ -99,7 +99,7 @@ func CreateRelease(ctx *context.APIContext, form api.CreateReleaseOption) { IsPrerelease: form.IsPrerelease, CreatedUnix: commit.Author.When.Unix(), } - if err := models.CreateRelease(ctx.Repo.GitRepo, rel); err != nil { + if err := models.CreateRelease(ctx.Repo.GitRepo, rel, nil); err != nil { if models.IsErrReleaseAlreadyExist(err) { ctx.Status(409) } else { @@ -145,7 +145,7 @@ func EditRelease(ctx *context.APIContext, form api.EditReleaseOption) { if form.IsPrerelease != nil { rel.IsPrerelease = *form.IsPrerelease } - if err := models.UpdateRelease(ctx.Repo.GitRepo, rel); err != nil { + if err := models.UpdateRelease(ctx.Repo.GitRepo, rel, nil); err != nil { ctx.Error(500, "UpdateRelease", err) return } diff --git a/routers/repo/issue.go b/routers/repo/issue.go index a43a3d992bc..f77b44f6337 100644 --- a/routers/repo/issue.go +++ b/routers/repo/issue.go @@ -477,8 +477,8 @@ func NewIssuePost(ctx *context.Context, form auth.CreateIssueForm) { ctx.Redirect(ctx.Repo.RepoLink + "/issues/" + com.ToStr(issue.Index)) } -// UploadIssueAttachment response for uploading issue's attachment -func UploadIssueAttachment(ctx *context.Context) { +// UploadAttachment response for uploading issue's attachment +func UploadAttachment(ctx *context.Context) { if !setting.AttachmentEnabled { ctx.Error(404, "attachment is not enabled") return diff --git a/routers/repo/release.go b/routers/repo/release.go index 01ed3c37a11..3e0fc94e4e2 100644 --- a/routers/repo/release.go +++ b/routers/repo/release.go @@ -15,6 +15,7 @@ import ( "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/markdown" + "code.gitea.io/gitea/modules/setting" "github.com/Unknwon/paginater" ) @@ -99,6 +100,12 @@ func Releases(ctx *context.Context) { return } + err = models.GetReleaseAttachments(releases...) + if err != nil { + ctx.Handle(500, "GetReleaseAttachments", err) + return + } + // Temproray cache commits count of used branches to speed up. countCache := make(map[string]int64) var cacheUsers = make(map[int64]*models.User) @@ -162,6 +169,7 @@ func NewRelease(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("repo.release.new_release") ctx.Data["PageIsReleaseList"] = true ctx.Data["tag_target"] = ctx.Repo.Repository.DefaultBranch + renderAttachmentSettings(ctx); ctx.HTML(200, tplReleaseNew) } @@ -215,7 +223,12 @@ func NewReleasePost(ctx *context.Context, form auth.NewReleaseForm) { CreatedUnix: tagCreatedUnix, } - if err = models.CreateRelease(ctx.Repo.GitRepo, rel); err != nil { + var attachmentUUIDs []string + if setting.AttachmentEnabled { + attachmentUUIDs = form.Files + } + + if err = models.CreateRelease(ctx.Repo.GitRepo, rel, attachmentUUIDs); err != nil { ctx.Data["Err_TagName"] = true switch { case models.IsErrReleaseAlreadyExist(err): @@ -237,6 +250,7 @@ func EditRelease(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("repo.release.edit_release") ctx.Data["PageIsReleaseList"] = true ctx.Data["PageIsEditRelease"] = true + renderAttachmentSettings(ctx); tagName := ctx.Params("*") rel, err := models.GetRelease(ctx.Repo.Repository.ID, tagName) @@ -286,11 +300,16 @@ func EditReleasePost(ctx *context.Context, form auth.EditReleaseForm) { return } + var attachmentUUIDs []string + if setting.AttachmentEnabled { + attachmentUUIDs = form.Files + } + rel.Title = form.Title rel.Note = form.Content rel.IsDraft = len(form.Draft) > 0 rel.IsPrerelease = form.Prerelease - if err = models.UpdateRelease(ctx.Repo.GitRepo, rel); err != nil { + if err = models.UpdateRelease(ctx.Repo.GitRepo, rel, attachmentUUIDs); err != nil { ctx.Handle(500, "UpdateRelease", err) return } diff --git a/templates/repo/issue/comment_tab.tmpl b/templates/repo/issue/comment_tab.tmpl index b009060cd19..3af3d8a041e 100644 --- a/templates/repo/issue/comment_tab.tmpl +++ b/templates/repo/issue/comment_tab.tmpl @@ -13,5 +13,5 @@ {{if .IsAttachmentEnabled}}
-
+
{{end}} diff --git a/templates/repo/release/list.tmpl b/templates/repo/release/list.tmpl index 4879e04811d..c18bc4884d2 100644 --- a/templates/repo/release/list.tmpl +++ b/templates/repo/release/list.tmpl @@ -59,6 +59,15 @@
  • {{$.i18n.Tr "repo.release.source_code"}} (TAR.GZ)
  • + {{if .Attachments}} + {{range .Attachments}} +
  • + + {{.Name}} + +
  • + {{end}} + {{end}} {{else}} diff --git a/templates/repo/release/new.tmpl b/templates/repo/release/new.tmpl index 3a9e6fadd8b..5f82cf2f526 100644 --- a/templates/repo/release/new.tmpl +++ b/templates/repo/release/new.tmpl @@ -48,6 +48,10 @@ + {{if .IsAttachmentEnabled}} +
    +
    + {{end}}