1
0
Fork 0
mirror of https://github.com/anyproto/anytype-heart.git synced 2025-06-08 05:47:07 +09:00

GO-5364 deeplink paths for single object export

This commit is contained in:
Roman Khafizianov 2025-03-25 17:57:07 +01:00
parent 18206b6ecb
commit 276efcabb9
No known key found for this signature in database
GPG key ID: F07A7D55A2684852
4 changed files with 61 additions and 18 deletions

View file

@ -39,6 +39,7 @@ import (
"github.com/anyproto/anytype-heart/pkg/lib/bundle"
"github.com/anyproto/anytype-heart/pkg/lib/core/smartblock"
"github.com/anyproto/anytype-heart/pkg/lib/database"
"github.com/anyproto/anytype-heart/pkg/lib/gateway"
"github.com/anyproto/anytype-heart/pkg/lib/localstore/addr"
"github.com/anyproto/anytype-heart/pkg/lib/localstore/objectstore"
"github.com/anyproto/anytype-heart/pkg/lib/logging"
@ -82,6 +83,7 @@ type export struct {
accountService account.Service
notificationService notifications.Notifications
processService process.Service
gatewayService gateway.Gateway
}
func New() Export {
@ -97,6 +99,7 @@ func (e *export) Init(a *app.App) (err error) {
e.spaceService = app.MustComponent[space.Service](a)
e.accountService = app.MustComponent[account.Service](a)
e.notificationService = app.MustComponent[notifications.Notifications](a)
e.gatewayService = app.MustComponent[gateway.Gateway](a)
return
}
@ -115,7 +118,7 @@ func (e *export) Export(ctx context.Context, req pb.RpcObjectListExportRequest)
if err = queue.Start(); err != nil {
return
}
exportCtx := NewExportContext(e, req)
exportCtx := newExportContext(e, req)
return exportCtx.exportObjects(ctx, queue)
}
@ -129,7 +132,7 @@ func (e *export) ExportInMemory(ctx context.Context, spaceId string, objectIds [
}
res = make(map[string][]byte)
exportCtx := NewExportContext(e, req)
exportCtx := newExportContext(e, req)
for _, objectId := range objectIds {
b, err := exportCtx.exportObject(ctx, objectId)
if err != nil {
@ -191,11 +194,11 @@ type exportContext struct {
relations map[string]struct{}
setOfList map[string]struct{}
objectTypes map[string]struct{}
gatewayUrl string
*export
}
func NewExportContext(e *export, req pb.RpcObjectListExportRequest) *exportContext {
func newExportContext(e *export, req pb.RpcObjectListExportRequest) *exportContext {
ec := &exportContext{
path: req.Path,
spaceId: req.SpaceId,
@ -213,8 +216,8 @@ func NewExportContext(e *export, req pb.RpcObjectListExportRequest) *exportConte
setOfList: make(map[string]struct{}),
objectTypes: make(map[string]struct{}),
relations: make(map[string]struct{}),
export: e,
gatewayUrl: "http://" + e.gatewayService.Addr(),
export: e,
}
return ec
}
@ -256,7 +259,13 @@ func (e *exportContext) exportObject(ctx context.Context, objectId string) ([]by
return nil, err
}
inMemoryWriter := &InMemoryWriter{}
var docNamer Namer
if e.format == model.Export_Markdown {
docNamer = &deepLinkNamer{gatewayUrl: e.gatewayUrl}
} else {
docNamer = newNamer()
}
inMemoryWriter := &InMemoryWriter{fn: docNamer}
err = e.writeDoc(ctx, inMemoryWriter, objectId, e.docs.transformToDetailsMap())
if err != nil {
return nil, err

View file

@ -4,22 +4,28 @@ import (
"archive/zip"
"fmt"
"io"
"net/url"
"os"
"path"
"path/filepath"
"sync"
"time"
"github.com/anyproto/anytype-heart/pkg/lib/mill"
"github.com/anyproto/anytype-heart/util/anyerror"
)
type writer interface {
Path() string
Namer() *namer
Namer() Namer
WriteFile(filename string, r io.Reader, lastModifiedDate int64) (err error)
Close() (err error)
}
type Namer interface {
Get(path, hash, title, ext string) (name string)
}
func uniqName() string {
return time.Now().Format("Anytype.20060102.150405.99")
}
@ -44,7 +50,7 @@ type dirWriter struct {
m sync.Mutex
}
func (d *dirWriter) Namer() *namer {
func (d *dirWriter) Namer() Namer {
d.m.Lock()
defer d.m.Unlock()
if d.fn == nil {
@ -108,7 +114,7 @@ type zipWriter struct {
fn *namer
}
func (d *zipWriter) Namer() *namer {
func (d *zipWriter) Namer() Namer {
d.m.Lock()
defer d.m.Unlock()
if d.fn == nil {
@ -152,16 +158,11 @@ func getZipName(path string) string {
type InMemoryWriter struct {
data map[string][]byte
fn *namer
fn Namer
m sync.Mutex
}
func (d *InMemoryWriter) Namer() *namer {
d.m.Lock()
defer d.m.Unlock()
if d.fn == nil {
d.fn = newNamer()
}
func (d *InMemoryWriter) Namer() Namer {
return d.fn
}
@ -192,3 +193,28 @@ func (d *InMemoryWriter) GetData(id string) []byte {
defer d.m.Unlock()
return d.data[id]
}
// deepLinkNamer used to render a single-object export, in md format
type deepLinkNamer struct {
gatewayUrl string
}
func (fn *deepLinkNamer) Get(path, hash, title, ext string) (name string) {
if ext == ".md" {
// object links via deeplink to the app
return "anytype://object?objectId=" + hash
}
// files links via gateway
u, err := url.Parse(fn.gatewayUrl)
if err != nil {
return "anytype://object?objectId=" + hash
}
if mill.IsImageExt(ext) {
u.Path = "image/" + hash
} else {
u.Path = "file/" + hash
}
return u.String()
}

View file

@ -98,7 +98,7 @@ func ConvertTextToFile(filePath string) *model.BlockContentOfFile {
return nil
}
imageFormats := []string{"jpg", "jpeg", "png", "gif", "webp"}
imageFormats := []string{"jpg", "jpeg", "png", "gif", "webp", "heic", "heif", "bmp", "tiff", "psd", "ico"}
videoFormats := []string{"mp4", "m4v", "mov"}
audioFormats := []string{"mp3", "ogg", "wav", "m4a", "flac"}
pdfFormat := "pdf"

View file

@ -42,6 +42,14 @@ const (
TIFF Format = "tiff"
)
func IsImageExt(ext string) bool {
switch strings.ToLower(strings.TrimPrefix(ext, ".")) {
case "jpg", "jpeg", "png", "gif", "ico", "webp", "heic", "heif", "bmp", "tiff", "psd":
return true
}
return false
}
func IsImage(mime string) bool {
parts := strings.SplitN(mime, "/", 2)
if len(parts) == 1 {