mark/pkg/mark/attachment.go

256 lines
4.8 KiB
Go
Raw Normal View History

2019-04-19 10:31:41 +03:00
package mark
2019-04-19 17:35:05 +03:00
import (
2019-04-20 10:24:30 +03:00
"bytes"
2019-04-19 17:35:05 +03:00
"crypto/sha256"
"encoding/hex"
"io"
2019-04-20 10:24:30 +03:00
"net/url"
2019-04-19 17:35:05 +03:00
"os"
2019-04-22 13:52:10 +03:00
"path"
2019-04-19 17:35:05 +03:00
"path/filepath"
2019-04-20 10:24:30 +03:00
"sort"
2019-04-19 17:35:05 +03:00
"strings"
"github.com/kovetskiy/mark/pkg/confluence"
"github.com/reconquest/karma-go"
2020-11-03 17:12:51 +03:00
"github.com/reconquest/pkg/log"
2019-04-19 17:35:05 +03:00
)
const (
AttachmentChecksumPrefix = `mark:checksum: `
)
2019-04-19 10:31:41 +03:00
type Attachment struct {
2019-04-19 17:35:05 +03:00
ID string
Name string
Filename string
Path string
Checksum string
Link string
2020-06-25 00:36:08 -05:00
Replace string
2019-04-19 10:31:41 +03:00
}
func ResolveAttachments(
api *confluence.API,
page *confluence.PageInfo,
base string,
replacements []string,
2019-04-20 10:24:30 +03:00
) ([]Attachment, error) {
attaches, err := prepareAttachments(base, replacements)
if err != nil {
return nil, err
}
2019-04-19 17:35:05 +03:00
for _, attach := range attaches {
2019-04-19 17:35:05 +03:00
checksum, err := getChecksum(attach.Path)
if err != nil {
2019-04-20 10:24:30 +03:00
return nil, karma.Format(
2019-04-19 17:35:05 +03:00
err,
"unable to get checksum for attachment: %q", attach.Name,
)
}
attach.Checksum = checksum
}
remotes, err := api.GetAttachments(page.ID)
2019-04-19 10:31:41 +03:00
if err != nil {
panic(err)
}
2019-04-19 17:35:05 +03:00
existing := []Attachment{}
creating := []Attachment{}
updating := []Attachment{}
2019-04-20 10:24:30 +03:00
for _, attach := range attaches {
2019-04-19 17:35:05 +03:00
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
2019-04-22 13:52:10 +03:00
attach.Link = path.Join(
remote.Links.Context,
remote.Links.Download,
)
2019-04-19 17:35:05 +03:00
found = true
break
}
}
if found {
if same {
existing = append(existing, attach)
} else {
updating = append(updating, attach)
}
} else {
creating = append(creating, attach)
}
}
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 {
2019-04-20 10:24:30 +03:00
return nil, karma.Format(
2019-04-19 17:35:05 +03:00
err,
"unable to create attachment %q",
attach.Name,
)
}
attach.ID = info.ID
2019-04-22 13:52:10 +03:00
attach.Link = path.Join(
info.Links.Context,
info.Links.Download,
)
2019-04-19 17:35:05 +03:00
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 {
2019-04-20 10:24:30 +03:00
return nil, karma.Format(
2019-04-19 17:35:05 +03:00
err,
"unable to update attachment %q",
attach.Name,
)
}
2019-04-22 13:52:10 +03:00
attach.Link = path.Join(
info.Links.Context,
info.Links.Download,
)
2019-04-19 17:35:05 +03:00
updating[i] = attach
}
2019-04-20 10:24:30 +03:00
attaches = []Attachment{}
attaches = append(attaches, existing...)
attaches = append(attaches, creating...)
attaches = append(attaches, updating...)
return attaches, nil
}
func prepareAttachments(base string, replacements []string) ([]Attachment, error) {
attaches := []Attachment{}
for _, name := range replacements {
attach := Attachment{
Name: name,
Filename: strings.ReplaceAll(name, "/", "_"),
Path: filepath.Join(base, name),
Replace: name,
}
attaches = append(attaches, attach)
}
return attaches, nil
}
2019-04-20 10:24:30 +03:00
func CompileAttachmentLinks(markdown []byte, attaches []Attachment) []byte {
links := map[string]string{}
2020-06-25 00:36:08 -05:00
replaces := []string{}
2019-04-20 10:24:30 +03:00
for _, attach := range attaches {
uri, err := url.ParseRequestURI(attach.Link)
if err != nil {
2020-06-25 00:36:08 -05:00
links[attach.Replace] = strings.ReplaceAll("&", "&", attach.Link)
2019-04-20 10:24:30 +03:00
} else {
2020-06-25 00:36:08 -05:00
links[attach.Replace] = uri.Path +
2019-04-20 10:24:30 +03:00
"?" + url.QueryEscape(uri.Query().Encode())
}
2020-06-25 00:36:08 -05:00
replaces = append(replaces, attach.Replace)
2019-04-20 10:24:30 +03:00
}
// sort by length so first items will have bigger length
// it's helpful for replacing in case of following names
// attachments/a.jpg
// attachments/a.jpg.jpg
// so we replace longer and then shorter
2020-06-25 00:36:08 -05:00
sort.SliceStable(replaces, func(i, j int) bool {
return len(replaces[i]) > len(replaces[j])
2019-04-20 10:24:30 +03:00
})
2020-06-25 00:36:08 -05:00
for _, replace := range replaces {
to := links[replace]
2019-04-20 10:24:30 +03:00
found := false
if bytes.Contains(markdown, []byte("attachment://"+replace)) {
from := "attachment://" + replace
2019-04-20 10:24:30 +03:00
log.Debugf(nil, "replacing legacy link: %q -> %q", from, to)
markdown = bytes.ReplaceAll(
markdown,
[]byte(from),
[]byte(to),
)
found = true
}
if bytes.Contains(markdown, []byte(replace)) {
from := replace
log.Debugf(nil, "replacing link: %q -> %q", from, to)
markdown = bytes.ReplaceAll(
markdown,
[]byte(from),
[]byte(to),
)
found = true
}
if !found {
log.Warningf(nil, "unused attachment: %s", replace)
}
2019-04-20 10:24:30 +03:00
}
return markdown
2019-04-19 17:35:05 +03:00
}
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
2019-04-19 10:31:41 +03:00
}