<commit_before>package note

import (
	"fmt"
	"time"

	"github.com/cozy/cozy-stack/model/instance"
	"github.com/cozy/cozy-stack/model/vfs"
	"github.com/cozy/cozy-stack/pkg/consts"
	"github.com/cozy/cozy-stack/pkg/couchdb"
	"github.com/cozy/cozy-stack/pkg/jsonapi"
	"github.com/cozy/prosemirror-go/transform"
)

// Step is a patch to apply on a note.
type Step map[string]interface{}

// ID returns the step qualified identifier
func (s Step) ID() string {
	id, _ := s["_id"].(string)
	return id
}

// Rev returns the step revision
func (s Step) Rev() string {
	rev, _ := s["_rev"].(string)
	return rev
}

// DocType returns the document type
func (s Step) DocType() string { return consts.NotesSteps }

// Clone implements couchdb.Doc
func (s Step) Clone() couchdb.Doc {
	cloned := make(Step)
	for k, v := range s {
		cloned[k] = v
	}
	return cloned
}

// SetID changes the step qualified identifier
func (s Step) SetID(id string) {
	if id == "" {
		delete(s, "_id")
	} else {
		s["_id"] = id
	}
}

// SetRev changes the step revision
func (s Step) SetRev(rev string) {
	if rev == "" {
		delete(s, "_rev")
	} else {
		s["_rev"] = rev
	}
}

// Included is part of the jsonapi.Object interface
func (s Step) Included() []jsonapi.Object { return nil }

// Links is part of the jsonapi.Object interface
func (s Step) Links() *jsonapi.LinksList { return nil }

// Relationships is part of the jsonapi.Object interface
func (s Step) Relationships() jsonapi.RelationshipMap { return nil }

func (s Step) timestamp() int64 {
	switch t := s["timestamp"].(type) {
	case float64:
		return int64(t)
	case int64:
		return t
	}
	return 0
}

func stepID(noteID string, version int64) string {
	return fmt.Sprintf("%s/%08d", noteID, version)
}

func endkey(noteID string) string {
	return fmt.Sprintf("%s/%s", noteID, couchdb.MaxString)
}

// GetSteps returns the steps for the given note, starting from the version.
func GetSteps(inst *instance.Instance, fileID string, version int64) ([]Step, error) {
	lock := inst.NotesLock()
	if err := lock.Lock(); err != nil {
		return nil, err
	}
	defer lock.Unlock()

	return getSteps(inst, fileID, version)
}

// getSteps is the same as GetSteps, but with the notes lock already acquired
func getSteps(inst *instance.Instance, fileID string, version int64) ([]Step, error) {
	var steps []Step
	req := couchdb.AllDocsRequest{
		Limit:    1000,
		StartKey: stepID(fileID, version),
		EndKey:   endkey(fileID),
	}
	if err := couchdb.GetAllDocs(inst, consts.NotesSteps, &req, &steps); err != nil {
		return nil, err
	}

	// The first step plays the role of a sentinel: if it isn't here, the
	// version is too old. Same if we have too many steps.
	if len(steps) == 0 || len(steps) == req.Limit {
		return nil, ErrTooOld
	}

	steps = steps[1:] // Discard the sentinel
	return steps, nil
}
<commit_msg>Fix issue with multi-bytes runes in notes<commit_after>package note

import (
	"fmt"
	"time"

	"github.com/cozy/cozy-stack/model/instance"
	"github.com/cozy/cozy-stack/model/vfs"
	"github.com/cozy/cozy-stack/pkg/consts"
	"github.com/cozy/cozy-stack/pkg/couchdb"
	"github.com/cozy/cozy-stack/pkg/jsonapi"
	"github.com/cozy/prosemirror-go/transform"
)

// Step is a patch to apply on a note.
type Step map[string]interface{}

// ID returns the step qualified identifier
func (s Step) ID() string {
	id, _ := s["_id"].(string)
	return id
}

// Rev returns the step revision
func (s Step) Rev() string {
	rev, _ := s["_rev"].(string)
	return rev
}

// DocType returns the document type
func (s Step) DocType() string { return consts.NotesSteps }

// Clone implements couchdb.Doc
func (s Step) Clone() couchdb.Doc {
	cloned := make(Step)
	for k, v := range s {
		cloned[k] = v
	}
	return cloned
}

// SetID changes the step qualified identifier
func (s Step) SetID(id string) {
	if id == "" {
		delete(s, "_id")
	} else {
		s["_id"] = id
	}
}

// SetRev changes the step revision
func (s Step) SetRev(rev string) {
	if rev == "" {
		delete(s, "_rev")
	} else {
		s["_rev"] = rev
	}
}

// Included is part of the jsonapi.Object interface
func (s Step) Included() []jsonapi.Object { return nil }

// Links is part of the jsonapi.Object interface
func (s Step) Links() *jsonapi.LinksList { return nil }

// Relationships is part of the jsonapi.Object interface
func (s Step) Relationships() jsonapi.RelationshipMap { return nil }

func (s Step) timestamp() int64 {
	switch t := s["timestamp"].(type) {
	case float64:
		return int64(t)
	case int64:
		return t
	}
	return 0
}

func stepID(noteID string, version int64) string {
	return fmt.Sprintf("%s/%08d", noteID, version)
}

func endkey(noteID string) string {
	return fmt.Sprintf("%s/%s", noteID, couchdb.MaxString)
}

// GetSteps returns the steps for the given note, starting from the version.
func GetSteps(inst *instance.Instance, fileID string, version int64) ([]Step, error) {
	lock := inst.NotesLock()
	if err := lock.Lock(); err != nil {
		return nil, err
	}
	defer lock.Unlock()

	return getSteps(inst, fileID, version)
}

// getSteps is the same as GetSteps, but with the notes lock already acquired
func getSteps(inst *instance.Instance, fileID string, version int64) ([]Step, error) {
	var steps []Step
	req := couchdb.AllDocsRequest{
		Limit:    1000,
		StartKey: stepID(fileID, version),
		EndKey:   endkey(fileID),
	}
	if err := couchdb.GetAllDocs(inst, consts.NotesSteps, &req, &steps); err != nil {
		return nil, err
	}

	// The first step plays the role of a sentinel: if it isn't here, the
	// version is too old. Same if we have too many steps.
	if len(steps) == 0 || len(steps) == req.Limit {
		return nil, ErrTooOld
	}

	if version > 0 {
		steps = steps[1:] // Discard the sentinel
	}
	return steps, nil
}

{'fraction_non_alphanumeric': 0.1365870786516854, 'fraction_numerical': 0.008251404494382022, 'mean_word_length': 3.6966199505358617, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 4, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6373138', 'n_tokens_mistral': 2112, 'n_tokens_neox': 2021, 'n_words': 647}