Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Import notes' images from revoked sharings #4339

Merged
merged 4 commits into from
Feb 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 64 additions & 9 deletions model/note/import.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"archive/tar"
"bytes"
"errors"
"fmt"
"io"
"path"
"strconv"
Expand Down Expand Up @@ -43,7 +44,7 @@ func ImportFile(inst *instance.Instance, newdoc, olddoc *vfs.FileDoc, body io.Re
}

reader := io.TeeReader(body, file)
content, err := importReader(inst, newdoc, reader, schema)
content, _, err := importReader(inst, newdoc, reader, schema)

if content != nil {
fillMetadata(newdoc, olddoc, schemaSpecs, content)
Expand All @@ -67,12 +68,64 @@ func ImportFile(inst *instance.Instance, newdoc, olddoc *vfs.FileDoc, body io.Re
return nil
}

func importReader(inst *instance.Instance, doc *vfs.FileDoc, reader io.Reader, schema *model.Schema) (*model.Node, error) {
func ImportImages(inst *instance.Instance, olddoc *vfs.FileDoc) error {
inst.Logger().WithNamespace("notes").
Infof("importing images from note: %s", olddoc.ID())
schemaSpecs := DefaultSchemaSpecs()
specs := model.SchemaSpecFromJSON(schemaSpecs)
schema, err := model.NewSchema(&specs)
if err != nil {
return fmt.Errorf("failed to read note schema: %w", err)
}

fs := inst.VFS()
file, err := fs.OpenFile(olddoc)
if err != nil {
return fmt.Errorf("failed to open file for note images import: %w", err)
}

content, images, err := importReader(inst, olddoc, file, schema)
cleanImages(inst, images) // XXX: remove images found in the archive but not in the markdown
if cerr := file.Close(); cerr != nil {
return fmt.Errorf("error while closing note file: %w", cerr)
}
if content == nil || !hasImages(images) {
inst.Logger().WithNamespace("notes").
Infof("No images to import")
return nil
}

md := markdownSerializer(images).Serialize(content)
body, err := buildArchive(inst, []byte(md), images)
if err != nil {
return fmt.Errorf("failed to build note archive: %w", err)
}
newdoc := olddoc.Clone().(*vfs.FileDoc)
newdoc.ByteSize = int64(len(body))
newdoc.MD5Sum = nil
fillMetadata(newdoc, olddoc, schemaSpecs, content)
taratatach marked this conversation as resolved.
Show resolved Hide resolved

file, err = inst.VFS().CreateFile(newdoc, olddoc)
if err != nil {
return fmt.Errorf("failed to create file for note images import: %w", err)
}
_, err = file.Write(body)
if err != nil {
err = fmt.Errorf("failed to write updated note: %w", err)
}
if cerr := file.Close(); cerr != nil && err == nil {
err = fmt.Errorf("failed to close updated note file: %w", cerr)
}

return err
}

func importReader(inst *instance.Instance, doc *vfs.FileDoc, reader io.Reader, schema *model.Schema) (*model.Node, []*Image, error) {
buf := &bytes.Buffer{}
var hasImages bool
if _, err := io.CopyN(buf, reader, 512); err != nil {
if !errors.Is(err, io.EOF) {
return nil, err
return nil, nil, fmt.Errorf("failed to buffer note content: %w", err)
}
hasImages = false
} else {
Expand All @@ -81,9 +134,10 @@ func importReader(inst *instance.Instance, doc *vfs.FileDoc, reader io.Reader, s

if !hasImages {
if _, err := buf.ReadFrom(reader); err != nil {
return nil, err
return nil, nil, err
}
return parseFile(buf, schema)
content, err := parseFile(buf, schema)
return content, nil, err
}

var content *model.Node
Expand All @@ -99,26 +153,26 @@ func importReader(inst *instance.Instance, doc *vfs.FileDoc, reader io.Reader, s
for {
header, errh := tr.Next()
if errh != nil {
return content, err
return content, images, errh
}
if header.Typeflag != tar.TypeReg {
continue
}
if header.Name == "index.md" {
content, err = parseFile(tr, schema)
if err != nil {
return nil, err
return nil, nil, fmt.Errorf("failed to parse note markdown: %w", err)
}
} else {
ext := path.Ext(header.Name)
contentType := filetype.ByExtension(ext)
upload, erru := NewImageUpload(inst, doc, header.Name, contentType)
if erru != nil {
err = erru
err = fmt.Errorf("failed to create image upload for %s: %w", header.Name, erru)
} else {
_, errc := io.Copy(upload, tr)
if cerr := upload.Close(); cerr != nil && (errc == nil || errc == io.ErrUnexpectedEOF) {
errc = cerr
errc = fmt.Errorf("failed to upload image %s: %w", header.Name, cerr)
}
if errc != nil {
err = errc
Expand All @@ -136,6 +190,7 @@ func fixURLForProsemirrorImages(node *model.Node, images []*Image) {
for _, img := range images {
if img.originalName == name {
node.Attrs["url"] = img.DocID
img.seen = true
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions model/note/note.go
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ func Create(inst *instance.Instance, doc *Document) (*vfs.FileDoc, error) {
if err != nil {
return nil, err
}
if err := setupTrigger(inst, file.ID()); err != nil {
if err := SetupTrigger(inst, file.ID()); err != nil {
return nil, err
}
return file, nil
Expand Down Expand Up @@ -341,7 +341,7 @@ type DebounceMessage struct {
NoteID string `json:"note_id"`
}

func setupTrigger(inst *instance.Instance, fileID string) error {
func SetupTrigger(inst *instance.Instance, fileID string) error {
sched := job.System()
infos := job.TriggerInfos{
Type: "@event",
Expand Down
6 changes: 3 additions & 3 deletions model/office/callback.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ func (c *callbackClaims) GetAudience() (jwt.ClaimStrings, error) { return

// Callback will manage the callback from the document server.
func Callback(inst *instance.Instance, params CallbackParameters) error {
cfg := getConfig(inst.ContextName)
cfg := GetConfig(inst.ContextName)
if err := checkToken(cfg, params); err != nil {
return err
}
Expand Down Expand Up @@ -132,7 +132,7 @@ func forceSaveFile(inst *instance.Instance, key, downloadURL string) error {
}

// saveFile saves the file with content from the given URL and returns the new revision.
func saveFile(inst *instance.Instance, detector conflictDetector, downloadURL string) (*conflictDetector, error) {
func saveFile(inst *instance.Instance, detector ConflictDetector, downloadURL string) (*ConflictDetector, error) {
fs := inst.VFS()
file, err := fs.FileByID(detector.ID)
if err != nil {
Expand Down Expand Up @@ -197,6 +197,6 @@ func saveFile(inst *instance.Instance, detector conflictDetector, downloadURL st
if cerr := f.Close(); cerr != nil && err == nil {
err = cerr
}
updated := conflictDetector{ID: newfile.ID(), Rev: newfile.Rev(), MD5Sum: newfile.MD5Sum}
updated := ConflictDetector{ID: newfile.ID(), Rev: newfile.Rev(), MD5Sum: newfile.MD5Sum}
return &updated, err
}
13 changes: 13 additions & 0 deletions model/office/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package office

import "github.com/cozy/cozy-stack/pkg/config/config"

func GetConfig(contextName string) *config.Office {
configuration := config.GetConfig().Office
if c, ok := configuration[contextName]; ok {
return &c
} else if c, ok := configuration[config.DefaultInstanceContext]; ok {
return &c
}
return nil
}
2 changes: 1 addition & 1 deletion model/office/file_by_key.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ func EnsureFileForKey(inst *instance.Instance, key string) (*vfs.FileDoc, error)
return nil, err
}

updated := conflictDetector{ID: newfile.ID(), Rev: newfile.Rev(), MD5Sum: newfile.MD5Sum}
updated := ConflictDetector{ID: newfile.ID(), Rev: newfile.Rev(), MD5Sum: newfile.MD5Sum}
_ = GetStore().UpdateSecret(inst, key, file.ID(), newfile.ID())
_ = GetStore().UpdateDoc(inst, key, updated)
return newfile, nil
Expand Down
24 changes: 12 additions & 12 deletions model/office/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import (
"github.com/redis/go-redis/v9"
)

type conflictDetector struct {
type ConflictDetector struct {
ID string
Rev string
MD5Sum []byte
Expand All @@ -24,9 +24,9 @@ type conflictDetector struct {
type Store interface {
GetSecretByID(db prefixer.Prefixer, id string) (string, error)
UpdateSecret(db prefixer.Prefixer, secret, oldID, newID string) error
AddDoc(db prefixer.Prefixer, payload conflictDetector) (string, error)
GetDoc(db prefixer.Prefixer, secret string) (*conflictDetector, error)
UpdateDoc(db prefixer.Prefixer, secret string, payload conflictDetector) error
AddDoc(db prefixer.Prefixer, payload ConflictDetector) (string, error)
GetDoc(db prefixer.Prefixer, secret string) (*ConflictDetector, error)
UpdateDoc(db prefixer.Prefixer, secret string, payload ConflictDetector) error
RemoveDoc(db prefixer.Prefixer, secret string) error
}

Expand Down Expand Up @@ -57,7 +57,7 @@ func GetStore() Store {
}

type memRef struct {
val conflictDetector
val ConflictDetector
exp time.Time
}

Expand Down Expand Up @@ -106,7 +106,7 @@ func (s *memStore) UpdateSecret(db prefixer.Prefixer, secret, oldID, newID strin
return nil
}

func (s *memStore) AddDoc(db prefixer.Prefixer, payload conflictDetector) (string, error) {
func (s *memStore) AddDoc(db prefixer.Prefixer, payload ConflictDetector) (string, error) {
s.mu.Lock()
defer s.mu.Unlock()
secret := makeSecret()
Expand All @@ -119,7 +119,7 @@ func (s *memStore) AddDoc(db prefixer.Prefixer, payload conflictDetector) (strin
return secret, nil
}

func (s *memStore) GetDoc(db prefixer.Prefixer, secret string) (*conflictDetector, error) {
func (s *memStore) GetDoc(db prefixer.Prefixer, secret string) (*ConflictDetector, error) {
s.mu.Lock()
defer s.mu.Unlock()
key := docKey(db, secret)
Expand All @@ -134,7 +134,7 @@ func (s *memStore) GetDoc(db prefixer.Prefixer, secret string) (*conflictDetecto
return &ref.val, nil
}

func (s *memStore) UpdateDoc(db prefixer.Prefixer, secret string, payload conflictDetector) error {
func (s *memStore) UpdateDoc(db prefixer.Prefixer, secret string, payload ConflictDetector) error {
s.mu.Lock()
defer s.mu.Unlock()
if s.byID[payload.ID] != secret {
Expand Down Expand Up @@ -181,7 +181,7 @@ func (s *redisStore) UpdateSecret(db prefixer.Prefixer, secret, oldID, newID str
return s.c.Set(s.ctx, idKey, secret, storeTTL).Err()
}

func (s *redisStore) AddDoc(db prefixer.Prefixer, payload conflictDetector) (string, error) {
func (s *redisStore) AddDoc(db prefixer.Prefixer, payload ConflictDetector) (string, error) {
idKey := docKey(db, payload.ID)
v, err := json.Marshal(payload)
if err != nil {
Expand All @@ -198,7 +198,7 @@ func (s *redisStore) AddDoc(db prefixer.Prefixer, payload conflictDetector) (str
return secret, nil
}

func (s *redisStore) GetDoc(db prefixer.Prefixer, secret string) (*conflictDetector, error) {
func (s *redisStore) GetDoc(db prefixer.Prefixer, secret string) (*ConflictDetector, error) {
key := docKey(db, secret)
b, err := s.c.Get(s.ctx, key).Bytes()
if errors.Is(err, redis.Nil) {
Expand All @@ -207,14 +207,14 @@ func (s *redisStore) GetDoc(db prefixer.Prefixer, secret string) (*conflictDetec
if err != nil {
return nil, err
}
var val conflictDetector
var val ConflictDetector
if err = json.Unmarshal(b, &val); err != nil {
return nil, err
}
return &val, nil
}

func (s *redisStore) UpdateDoc(db prefixer.Prefixer, secret string, payload conflictDetector) error {
func (s *redisStore) UpdateDoc(db prefixer.Prefixer, secret string, payload ConflictDetector) error {
idKey := docKey(db, payload.ID)
result, err := s.c.Get(s.ctx, idKey).Result()
if err != nil {
Expand Down
66 changes: 66 additions & 0 deletions model/sharing/files.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (

"github.com/cozy/cozy-stack/client/request"
"github.com/cozy/cozy-stack/model/instance"
"github.com/cozy/cozy-stack/model/note"
"github.com/cozy/cozy-stack/model/vfs"
"github.com/cozy/cozy-stack/pkg/consts"
"github.com/cozy/cozy-stack/pkg/couchdb"
Expand Down Expand Up @@ -648,6 +649,71 @@ func (s *Sharing) ApplyBulkFiles(inst *instance.Instance, docs DocsList) error {
return errm
}

func (s *Sharing) GetNotes(inst *instance.Instance) ([]*vfs.FileDoc, error) {
rule := s.FirstFilesRule()
if rule != nil {
if rule.Mime != "" {
if rule.Mime == consts.NoteMimeType {
var notes []*vfs.FileDoc
req := &couchdb.AllDocsRequest{Keys: rule.Values}
if err := couchdb.GetAllDocs(inst, consts.Files, req, &notes); err != nil {
return nil, fmt.Errorf("failed to fetch notes shared by themselves: %w", err)
}

return notes, nil
} else {
return nil, nil
}
}

sharingDir, err := s.GetSharingDir(inst)
if err != nil {
return nil, fmt.Errorf("failed to get notes sharing dir: %w", err)
}

var notes []*vfs.FileDoc
fs := inst.VFS()
iter := fs.DirIterator(sharingDir, nil)
for {
_, f, err := iter.Next()
if errors.Is(err, vfs.ErrIteratorDone) {
break
}
if err != nil {
return nil, fmt.Errorf("failed to get next shared note: %w", err)
}
if f != nil && f.Mime == consts.NoteMimeType {
notes = append(notes, f)
}
}

return notes, nil
}

return nil, nil
}

func (s *Sharing) FixRevokedNotes(inst *instance.Instance) error {
docs, err := s.GetNotes(inst)
if err != nil {
return fmt.Errorf("failed to get revoked sharing notes: %w", err)
}

var errm error
for _, doc := range docs {
// If the note came from another cozy via a sharing that is now revoked, we
// may need to recreate the trigger.
if err := note.SetupTrigger(inst, doc.ID()); err != nil {
errm = multierror.Append(errm, fmt.Errorf("failed to setup revoked note trigger: %w", err))
}

if err := note.ImportImages(inst, doc); err != nil {
errm = multierror.Append(errm, fmt.Errorf("failed to import revoked note images: %w", err))
}
}
return errm
}

func removeReferencesFromRule(file *vfs.FileDoc, rule *Rule) {
if rule.Selector != couchdb.SelectorReferencedBy {
return
Expand Down
Loading
Loading