mirror of
https://github.com/kovetskiy/mark.git
synced 2025-04-24 05:42:40 +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
|
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(
|
err = api.UpdatePage(
|
||||||
target,
|
target,
|
||||||
|
@ -1,10 +1,14 @@
|
|||||||
package confluence
|
package confluence
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"mime/multipart"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
|
||||||
"github.com/bndr/gopencils"
|
"github.com/bndr/gopencils"
|
||||||
"github.com/kovetskiy/lorg"
|
"github.com/kovetskiy/lorg"
|
||||||
@ -12,20 +16,6 @@ import (
|
|||||||
"github.com/reconquest/karma-go"
|
"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
|
type RestrictionOperation string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -64,6 +54,36 @@ type PageInfo struct {
|
|||||||
} `json:"_links"`
|
} `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 {
|
func NewAPI(baseURL string, username string, password string) *API {
|
||||||
auth := &gopencils.BasicAuth{username, password}
|
auth := &gopencils.BasicAuth{username, password}
|
||||||
|
|
||||||
@ -92,11 +112,10 @@ func (api *API) FindRootPage(space string) (*PageInfo, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(page.Ancestors) == 0 {
|
if len(page.Ancestors) == 0 {
|
||||||
return nil, fmt.Errorf(
|
return &PageInfo{
|
||||||
"page %q from space %q has no parents",
|
ID: page.ID,
|
||||||
page.Title,
|
Title: page.Title,
|
||||||
space,
|
}, nil
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return &PageInfo{
|
return &PageInfo{
|
||||||
@ -139,8 +158,170 @@ func (api *API) FindPage(space string, title string) (*PageInfo, error) {
|
|||||||
return &result.Results[0], nil
|
return &result.Results[0], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (api *API) GetAttachments(pageID string) error {
|
func (api *API) CreateAttachment(
|
||||||
result := map[string]interface{}{}
|
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{
|
payload := map[string]string{
|
||||||
"expand": "version,container",
|
"expand": "version,container",
|
||||||
@ -150,19 +331,14 @@ func (api *API) GetAttachments(pageID string) error {
|
|||||||
"content/"+pageID+"/child/attachment", &result,
|
"content/"+pageID+"/child/attachment", &result,
|
||||||
).Get(payload)
|
).Get(payload)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if request.Raw.StatusCode != 200 {
|
if request.Raw.StatusCode != 200 {
|
||||||
return newErrorStatusNotOK(request)
|
return nil, newErrorStatusNotOK(request)
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
return result.Results, nil
|
||||||
marshaledXXX, _ := json.MarshalIndent(result, "", " ")
|
|
||||||
fmt.Printf("result: %s\n", string(marshaledXXX))
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (api *API) GetPageByID(pageID string) (*PageInfo, error) {
|
func (api *API) GetPageByID(pageID string) (*PageInfo, error) {
|
||||||
|
@ -1,9 +1,30 @@
|
|||||||
package mark
|
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 {
|
type Attachment struct {
|
||||||
Name string
|
ID string
|
||||||
|
Name string
|
||||||
|
Filename string
|
||||||
|
Path string
|
||||||
|
Checksum string
|
||||||
|
Link string
|
||||||
}
|
}
|
||||||
|
|
||||||
func ResolveAttachments(
|
func ResolveAttachments(
|
||||||
@ -11,9 +32,144 @@ func ResolveAttachments(
|
|||||||
page *confluence.PageInfo,
|
page *confluence.PageInfo,
|
||||||
base string,
|
base string,
|
||||||
names []string,
|
names []string,
|
||||||
) {
|
) error {
|
||||||
err := api.GetAttachments(page.ID)
|
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 {
|
if err != nil {
|
||||||
panic(err)
|
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