mirror of
https://github.com/kovetskiy/mark.git
synced 2025-04-23 21:32:41 +08:00
create/update attachment
This commit is contained in:
parent
76ddde31ab
commit
2abc2f122b
5
main.go
5
main.go
@ -187,7 +187,10 @@ func main() {
|
||||
target = page
|
||||
}
|
||||
|
||||
mark.ResolveAttachments(api, target, ".", meta.Attachments)
|
||||
err = mark.ResolveAttachments(api, target, ".", meta.Attachments)
|
||||
if err != nil {
|
||||
log.Fatalf(err, "unable to create/update attachments")
|
||||
}
|
||||
|
||||
err = api.UpdatePage(
|
||||
target,
|
||||
|
@ -1,10 +1,14 @@
|
||||
package confluence
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/bndr/gopencils"
|
||||
"github.com/kovetskiy/lorg"
|
||||
@ -12,20 +16,6 @@ import (
|
||||
"github.com/reconquest/karma-go"
|
||||
)
|
||||
|
||||
func discarder() *lorg.Log {
|
||||
stderr := lorg.NewLog()
|
||||
stderr.SetOutput(ioutil.Discard)
|
||||
return stderr
|
||||
}
|
||||
|
||||
var (
|
||||
log = cog.NewLogger(discarder())
|
||||
)
|
||||
|
||||
func SetLogger(logger *cog.Logger) {
|
||||
log = logger
|
||||
}
|
||||
|
||||
type RestrictionOperation string
|
||||
|
||||
const (
|
||||
@ -64,6 +54,36 @@ type PageInfo struct {
|
||||
} `json:"_links"`
|
||||
}
|
||||
|
||||
type AttachmentInfo struct {
|
||||
Filename string `json:"title"`
|
||||
ID string `json:"id"`
|
||||
Metadata struct {
|
||||
Comment string `json:"comment"`
|
||||
} `json:"metadata"`
|
||||
Links struct {
|
||||
Download string `json:"download"`
|
||||
} `json:"_links"`
|
||||
}
|
||||
|
||||
func discarder() *lorg.Log {
|
||||
stderr := lorg.NewLog()
|
||||
stderr.SetOutput(ioutil.Discard)
|
||||
return stderr
|
||||
}
|
||||
|
||||
var (
|
||||
log = cog.NewLogger(discarder())
|
||||
)
|
||||
|
||||
func SetLogger(logger *cog.Logger) {
|
||||
log = logger
|
||||
}
|
||||
|
||||
type form struct {
|
||||
buffer io.Reader
|
||||
writer *multipart.Writer
|
||||
}
|
||||
|
||||
func NewAPI(baseURL string, username string, password string) *API {
|
||||
auth := &gopencils.BasicAuth{username, password}
|
||||
|
||||
@ -92,11 +112,10 @@ func (api *API) FindRootPage(space string) (*PageInfo, error) {
|
||||
}
|
||||
|
||||
if len(page.Ancestors) == 0 {
|
||||
return nil, fmt.Errorf(
|
||||
"page %q from space %q has no parents",
|
||||
page.Title,
|
||||
space,
|
||||
)
|
||||
return &PageInfo{
|
||||
ID: page.ID,
|
||||
Title: page.Title,
|
||||
}, nil
|
||||
}
|
||||
|
||||
return &PageInfo{
|
||||
@ -139,8 +158,170 @@ func (api *API) FindPage(space string, title string) (*PageInfo, error) {
|
||||
return &result.Results[0], nil
|
||||
}
|
||||
|
||||
func (api *API) GetAttachments(pageID string) error {
|
||||
result := map[string]interface{}{}
|
||||
func (api *API) CreateAttachment(
|
||||
pageID string,
|
||||
name string,
|
||||
comment string,
|
||||
path string,
|
||||
) (AttachmentInfo, error) {
|
||||
var info AttachmentInfo
|
||||
|
||||
form, err := getAttachmentPayload(name, comment, path)
|
||||
if err != nil {
|
||||
return AttachmentInfo{}, err
|
||||
}
|
||||
|
||||
var result struct {
|
||||
Results []AttachmentInfo `json:"results"`
|
||||
}
|
||||
|
||||
resource := api.rest.Res(
|
||||
"content/"+pageID+"/child/attachment", &result,
|
||||
)
|
||||
|
||||
resource.Payload = form.buffer
|
||||
resource.Headers = http.Header{}
|
||||
|
||||
resource.SetHeader("Content-Type", form.writer.FormDataContentType())
|
||||
resource.SetHeader("X-Atlassian-Token", "no-check")
|
||||
|
||||
request, err := resource.Post()
|
||||
if err != nil {
|
||||
return info, err
|
||||
}
|
||||
|
||||
if request.Raw.StatusCode != 200 {
|
||||
return info, newErrorStatusNotOK(request)
|
||||
}
|
||||
|
||||
if len(result.Results) == 0 {
|
||||
return info, errors.New(
|
||||
"Confluence REST API for creating attachments returned " +
|
||||
"0 json objects, expected at least 1",
|
||||
)
|
||||
}
|
||||
|
||||
info = result.Results[0]
|
||||
|
||||
return info, nil
|
||||
}
|
||||
|
||||
func (api *API) UpdateAttachment(
|
||||
pageID string,
|
||||
attachID string,
|
||||
name string,
|
||||
comment string,
|
||||
path string,
|
||||
) (AttachmentInfo, error) {
|
||||
var info AttachmentInfo
|
||||
|
||||
form, err := getAttachmentPayload(name, comment, path)
|
||||
if err != nil {
|
||||
return AttachmentInfo{}, err
|
||||
}
|
||||
|
||||
var result struct {
|
||||
Results []AttachmentInfo `json:"results"`
|
||||
}
|
||||
|
||||
resource := api.rest.Res(
|
||||
"content/"+pageID+"/child/attachment/"+attachID+"/data", &result,
|
||||
)
|
||||
|
||||
resource.Payload = form.buffer
|
||||
resource.Headers = http.Header{}
|
||||
|
||||
resource.SetHeader("Content-Type", form.writer.FormDataContentType())
|
||||
resource.SetHeader("X-Atlassian-Token", "no-check")
|
||||
|
||||
request, err := resource.Post()
|
||||
if err != nil {
|
||||
return info, err
|
||||
}
|
||||
|
||||
//if request.Raw.StatusCode != 200 {
|
||||
return info, newErrorStatusNotOK(request)
|
||||
//}
|
||||
|
||||
if len(result.Results) == 0 {
|
||||
return info, errors.New(
|
||||
"Confluence REST API for creating attachments returned " +
|
||||
"0 json objects, expected at least 1",
|
||||
)
|
||||
}
|
||||
|
||||
info = result.Results[0]
|
||||
|
||||
return info, nil
|
||||
}
|
||||
|
||||
func getAttachmentPayload(name, comment, path string) (*form, error) {
|
||||
var (
|
||||
payload = bytes.NewBuffer(nil)
|
||||
writer = multipart.NewWriter(payload)
|
||||
)
|
||||
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, karma.Format(
|
||||
err,
|
||||
"unable to open file: %q",
|
||||
path,
|
||||
)
|
||||
}
|
||||
|
||||
defer file.Close()
|
||||
|
||||
content, err := writer.CreateFormFile("file", name)
|
||||
if err != nil {
|
||||
return nil, karma.Format(
|
||||
err,
|
||||
"unable to create form file",
|
||||
)
|
||||
}
|
||||
|
||||
_, err = io.Copy(content, file)
|
||||
if err != nil {
|
||||
return nil, karma.Format(
|
||||
err,
|
||||
"unable to copy i/o between form-file and file",
|
||||
)
|
||||
}
|
||||
|
||||
commentWriter, err := writer.CreateFormField("comment")
|
||||
if err != nil {
|
||||
return nil, karma.Format(
|
||||
err,
|
||||
"unable to create form field for comment",
|
||||
)
|
||||
}
|
||||
|
||||
_, err = commentWriter.Write([]byte(comment))
|
||||
if err != nil {
|
||||
return nil, karma.Format(
|
||||
err,
|
||||
"unable to write comment in form-field",
|
||||
)
|
||||
}
|
||||
|
||||
err = writer.Close()
|
||||
if err != nil {
|
||||
return nil, karma.Format(
|
||||
err,
|
||||
"unable to close form-writer",
|
||||
)
|
||||
}
|
||||
|
||||
return &form{
|
||||
buffer: payload,
|
||||
writer: writer,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (api *API) GetAttachments(pageID string) ([]AttachmentInfo, error) {
|
||||
result := struct {
|
||||
Results []AttachmentInfo `json:"results"`
|
||||
}{}
|
||||
|
||||
payload := map[string]string{
|
||||
"expand": "version,container",
|
||||
@ -150,19 +331,14 @@ func (api *API) GetAttachments(pageID string) error {
|
||||
"content/"+pageID+"/child/attachment", &result,
|
||||
).Get(payload)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if request.Raw.StatusCode != 200 {
|
||||
return newErrorStatusNotOK(request)
|
||||
return nil, newErrorStatusNotOK(request)
|
||||
}
|
||||
|
||||
{
|
||||
marshaledXXX, _ := json.MarshalIndent(result, "", " ")
|
||||
fmt.Printf("result: %s\n", string(marshaledXXX))
|
||||
}
|
||||
|
||||
return nil
|
||||
return result.Results, nil
|
||||
}
|
||||
|
||||
func (api *API) GetPageByID(pageID string) (*PageInfo, error) {
|
||||
|
@ -1,9 +1,30 @@
|
||||
package mark
|
||||
|
||||
import "github.com/kovetskiy/mark/pkg/confluence"
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/kovetskiy/mark/pkg/confluence"
|
||||
"github.com/reconquest/karma-go"
|
||||
)
|
||||
|
||||
const (
|
||||
AttachmentChecksumPrefix = `mark:checksum: `
|
||||
)
|
||||
|
||||
type Attachment struct {
|
||||
Name string
|
||||
ID string
|
||||
Name string
|
||||
Filename string
|
||||
Path string
|
||||
Checksum string
|
||||
Link string
|
||||
}
|
||||
|
||||
func ResolveAttachments(
|
||||
@ -11,9 +32,144 @@ func ResolveAttachments(
|
||||
page *confluence.PageInfo,
|
||||
base string,
|
||||
names []string,
|
||||
) {
|
||||
err := api.GetAttachments(page.ID)
|
||||
) error {
|
||||
attachs := []Attachment{}
|
||||
for _, name := range names {
|
||||
attach := Attachment{
|
||||
Name: name,
|
||||
Filename: strings.ReplaceAll(name, "/", "_"),
|
||||
Path: filepath.Join(base, name),
|
||||
}
|
||||
|
||||
checksum, err := getChecksum(attach.Path)
|
||||
if err != nil {
|
||||
return karma.Format(
|
||||
err,
|
||||
"unable to get checksum for attachment: %q", attach.Name,
|
||||
)
|
||||
}
|
||||
|
||||
attach.Checksum = checksum
|
||||
|
||||
attachs = append(attachs, attach)
|
||||
}
|
||||
|
||||
remotes, err := api.GetAttachments(page.ID)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
existing := []Attachment{}
|
||||
creating := []Attachment{}
|
||||
updating := []Attachment{}
|
||||
for _, attach := range attachs {
|
||||
var found bool
|
||||
var same bool
|
||||
for _, remote := range remotes {
|
||||
if remote.Filename == attach.Filename {
|
||||
same = attach.Checksum == strings.TrimPrefix(
|
||||
remote.Metadata.Comment,
|
||||
AttachmentChecksumPrefix,
|
||||
)
|
||||
|
||||
attach.ID = remote.ID
|
||||
attach.Link = remote.Links.Download
|
||||
|
||||
found = true
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if found {
|
||||
if same {
|
||||
existing = append(existing, attach)
|
||||
} else {
|
||||
updating = append(updating, attach)
|
||||
}
|
||||
} else {
|
||||
creating = append(creating, attach)
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
marshaledXXX, _ := json.MarshalIndent(existing, "", " ")
|
||||
fmt.Printf("existing: %s\n", string(marshaledXXX))
|
||||
}
|
||||
|
||||
{
|
||||
marshaledXXX, _ := json.MarshalIndent(creating, "", " ")
|
||||
fmt.Printf("creating: %s\n", string(marshaledXXX))
|
||||
}
|
||||
|
||||
{
|
||||
marshaledXXX, _ := json.MarshalIndent(updating, "", " ")
|
||||
fmt.Printf("updating: %s\n", string(marshaledXXX))
|
||||
}
|
||||
|
||||
for i, attach := range creating {
|
||||
log.Infof(nil, "creating attachment: %q", attach.Name)
|
||||
|
||||
info, err := api.CreateAttachment(
|
||||
page.ID,
|
||||
attach.Filename,
|
||||
AttachmentChecksumPrefix+attach.Checksum,
|
||||
attach.Path,
|
||||
)
|
||||
if err != nil {
|
||||
return karma.Format(
|
||||
err,
|
||||
"unable to create attachment %q",
|
||||
attach.Name,
|
||||
)
|
||||
}
|
||||
|
||||
attach.ID = info.ID
|
||||
attach.Link = info.Links.Download
|
||||
|
||||
creating[i] = attach
|
||||
}
|
||||
|
||||
for i, attach := range updating {
|
||||
log.Infof(nil, "updating attachment: %q", attach.Name)
|
||||
|
||||
info, err := api.UpdateAttachment(
|
||||
page.ID,
|
||||
attach.ID,
|
||||
attach.Name,
|
||||
AttachmentChecksumPrefix+attach.Checksum,
|
||||
attach.Path,
|
||||
)
|
||||
if err != nil {
|
||||
return karma.Format(
|
||||
err,
|
||||
"unable to update attachment %q",
|
||||
attach.Name,
|
||||
)
|
||||
}
|
||||
|
||||
attach.Link = info.Links.Download
|
||||
|
||||
updating[i] = attach
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getChecksum(filename string) (string, error) {
|
||||
file, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return "", karma.Format(
|
||||
err,
|
||||
"unable to open file",
|
||||
)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
hash := sha256.New()
|
||||
if _, err := io.Copy(hash, file); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return hex.EncodeToString(hash.Sum(nil)), nil
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user