grim/youtrack-import

Use the built in mime stuff to figure out the content type for attachments
package youtrack
import (
"fmt"
"io"
"mime"
"mime/multipart"
"net/http"
"net/textproto"
"net/url"
"path/filepath"
"strings"
"time"
)
type Attachment struct {
issue int
author string
created time.Time
payload *strings.Builder
writer *multipart.Writer
files []string
}
func NewAttachment(issue int, author string, created time.Time) *Attachment {
a := &Attachment{
issue: issue,
author: author,
created: created,
files: []string{},
}
a.payload = &strings.Builder{}
a.writer = multipart.NewWriter(a.payload)
return a
}
func (a *Attachment) Payload() (string, error) {
if err := a.writer.Close(); err != nil {
return "", err
}
return a.payload.String(), nil
}
var quoteEscaper = strings.NewReplacer("\\", "\\\\", `"`, "\\\"")
func escapeQuotes(s string) string {
return quoteEscaper.Replace(s)
}
func (a *Attachment) AddFile(r io.ReadCloser, filename string) error {
header := textproto.MIMEHeader{}
header.Set(
"Content-Disposition",
fmt.Sprintf("form-data; name=%q; filename=%q",
escapeQuotes(filename),
escapeQuotes(filename),
),
)
contentType := ""
ext := filepath.Ext(filename)
if ext != "" {
contentType = mime.TypeByExtension(ext)
}
if contentType == "" {
contentType = "application/octet-stream"
}
header.Set("Content-Type", contentType)
w, err := a.writer.CreatePart(header)
if err != nil {
return err
}
_, err = io.Copy(w, r)
if err != nil {
return err
}
defer r.Close()
a.files = append(a.files, filename)
return nil
}
func (a *Attachment) ContentType() string {
return a.writer.FormDataContentType()
}
func (c *Client) importAttachment(p *Project, a *Attachment) error {
values := url.Values{}
values.Add("authorLogin", a.author)
values.Add("created", formatTimeString(a.created))
payload, err := a.Payload()
if err != nil {
return err
}
reader := strings.NewReader(payload)
issueID := fmt.Sprintf("%s-%d", p.ID, a.issue)
resp, err := c.requestContentType(
http.MethodPost,
c.uri+"/import/"+issueID+"/attachment?"+values.Encode(),
a.ContentType(),
reader,
)
if err != nil {
return err
}
defer resp.Body.Close()
return c.validateXmlImportReport(
[]int{http.StatusOK, http.StatusCreated},
resp,
)
}
func (c *Client) ImportAttachments(p *Project) error {
for _, issue := range p.Issues {
for _, attachment := range issue.Attachments {
if err := c.importAttachment(p, attachment); err != nil {
return err
}
}
}
return nil
}