mark/attachment/attachment.go

295 lines
6.4 KiB
Go
Raw Normal View History

2023-09-01 22:59:04 +02:00
package attachment
2019-04-19 10:31:41 +03:00
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-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"
2024-09-26 15:24:39 +02:00
"github.com/kovetskiy/mark/confluence"
"github.com/kovetskiy/mark/vfs"
2019-04-19 17:35:05 +03:00
"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 {
ID string
Name string
Filename string
FileBytes []byte
Checksum string
Link string
Width string
Height string
Replace string
2019-04-19 10:31:41 +03:00
}
2023-09-06 16:19:09 -07:00
type Attacher interface {
Attach(Attachment)
}
2019-04-19 10:31:41 +03:00
func ResolveAttachments(
api *confluence.API,
page *confluence.PageInfo,
attachments []Attachment,
2019-04-20 10:24:30 +03:00
) ([]Attachment, error) {
for i := range attachments {
checksum, err := GetChecksum(bytes.NewReader(attachments[i].FileBytes))
2019-04-19 17:35:05 +03:00
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", attachments[i].Name,
2019-04-19 17:35:05 +03:00
)
}
attachments[i].Checksum = checksum
2019-04-19 17:35:05 +03:00
}
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{}
for _, attachment := range attachments {
2019-04-19 17:35:05 +03:00
var found bool
var same bool
for _, remote := range remotes {
if remote.Filename == attachment.Filename {
same = attachment.Checksum == strings.TrimPrefix(
2019-04-19 17:35:05 +03:00
remote.Metadata.Comment,
AttachmentChecksumPrefix,
)
attachment.ID = remote.ID
attachment.Link = path.Join(
2019-04-22 13:52:10 +03:00
remote.Links.Context,
remote.Links.Download,
)
2019-04-19 17:35:05 +03:00
found = true
break
}
}
if found {
if same {
existing = append(existing, attachment)
2019-04-19 17:35:05 +03:00
} else {
updating = append(updating, attachment)
2019-04-19 17:35:05 +03:00
}
} else {
creating = append(creating, attachment)
2019-04-19 17:35:05 +03:00
}
}
for i, attachment := range creating {
log.Infof(nil, "creating attachment: %q", attachment.Name)
2019-04-19 17:35:05 +03:00
info, err := api.CreateAttachment(
page.ID,
attachment.Filename,
AttachmentChecksumPrefix+attachment.Checksum,
bytes.NewReader(attachment.FileBytes),
2019-04-19 17:35:05 +03:00
)
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",
attachment.Name,
2019-04-19 17:35:05 +03:00
)
}
attachment.ID = info.ID
attachment.Link = path.Join(
2019-04-22 13:52:10 +03:00
info.Links.Context,
info.Links.Download,
)
2019-04-19 17:35:05 +03:00
creating[i] = attachment
2019-04-19 17:35:05 +03:00
}
for i, attachment := range updating {
log.Infof(nil, "updating attachment: %q", attachment.Name)
2019-04-19 17:35:05 +03:00
info, err := api.UpdateAttachment(
page.ID,
attachment.ID,
attachment.Filename,
AttachmentChecksumPrefix+attachment.Checksum,
bytes.NewReader(attachment.FileBytes),
2019-04-19 17:35:05 +03:00
)
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",
attachment.Name,
2019-04-19 17:35:05 +03:00
)
}
attachment.Link = path.Join(
2019-04-22 13:52:10 +03:00
info.Links.Context,
info.Links.Download,
)
2019-04-19 17:35:05 +03:00
updating[i] = attachment
2019-04-19 17:35:05 +03:00
}
2023-01-03 18:54:04 +01:00
for i := range existing {
log.Infof(nil, "keeping unmodified attachment: %q", attachments[i].Name)
}
attachments = []Attachment{}
attachments = append(attachments, existing...)
attachments = append(attachments, creating...)
attachments = append(attachments, updating...)
return attachments, nil
}
func ResolveLocalAttachments(opener vfs.Opener, base string, replacements []string) ([]Attachment, error) {
attachments, err := prepareAttachments(opener, base, replacements)
if err != nil {
return nil, err
2022-06-10 06:13:22 +02:00
}
for _, attachment := range attachments {
checksum, err := GetChecksum(bytes.NewReader(attachment.FileBytes))
if err != nil {
return nil, karma.Format(
err,
"unable to get checksum for attachment: %q", attachment.Name,
)
}
2019-04-20 10:24:30 +03:00
attachment.Checksum = checksum
}
return attachments, err
2019-04-20 10:24:30 +03:00
}
// prepareAttachements creates an array of attachement objects based on an array of filepaths
func prepareAttachments(opener vfs.Opener, base string, replacements []string) ([]Attachment, error) {
attachments := []Attachment{}
for _, name := range replacements {
attachment, err := prepareAttachment(opener, base, name)
if err != nil {
return nil, err
}
attachments = append(attachments, attachment)
}
return attachments, nil
}
// prepareAttachement opens the file, reads its content and creates an attachement object
func prepareAttachment(opener vfs.Opener, base, name string) (Attachment, error) {
attachmentPath := filepath.Join(base, name)
file, err := opener.Open(attachmentPath)
if err != nil {
return Attachment{}, karma.Format(err, "unable to open file: %q", attachmentPath)
}
defer file.Close()
fileBytes, err := io.ReadAll(file)
if err != nil {
return Attachment{}, karma.Format(err, "unable to read file: %q", attachmentPath)
}
return Attachment{
Name: name,
Filename: strings.ReplaceAll(name, "/", "_"),
FileBytes: fileBytes,
Replace: name,
}, nil
}
func CompileAttachmentLinks(markdown []byte, attachments []Attachment) []byte {
2019-04-20 10:24:30 +03:00
links := map[string]string{}
2020-06-25 00:36:08 -05:00
replaces := []string{}
2019-04-20 10:24:30 +03:00
for _, attachment := range attachments {
links[attachment.Replace] = parseAttachmentLink(attachment.Link)
replaces = append(replaces, attachment.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(reader io.Reader) (string, error) {
2019-04-19 17:35:05 +03:00
hash := sha256.New()
if _, err := io.Copy(hash, reader); err != nil {
2019-04-19 17:35:05 +03:00
return "", err
}
return hex.EncodeToString(hash.Sum(nil)), nil
2019-04-19 10:31:41 +03:00
}
func parseAttachmentLink(attachLink string) string {
uri, err := url.ParseRequestURI(attachLink)
if err != nil {
2023-05-05 14:43:36 +02:00
return strings.ReplaceAll(attachLink, "&", "&")
} else {
return uri.Path +
"?" + url.QueryEscape(uri.Query().Encode())
}
}