mirror of
https://github.com/schollz/cowyo.git
synced 2023-08-10 21:13:00 +03:00
263 lines
6.1 KiB
Go
263 lines
6.1 KiB
Go
|
package versionedtext
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"strconv"
|
||
|
"strings"
|
||
|
"time"
|
||
|
|
||
|
"github.com/sergi/go-diff/diffmatchpatch"
|
||
|
)
|
||
|
|
||
|
// VersionedText is the main container for the diff functions
|
||
|
type VersionedText struct {
|
||
|
CurrentText string
|
||
|
Diffs map[int64]string
|
||
|
}
|
||
|
|
||
|
// NewVersionedText returns a new VersionedText object
|
||
|
func NewVersionedText(text string) VersionedText {
|
||
|
data := VersionedText{CurrentText: "", Diffs: make(map[int64]string)}
|
||
|
data.Update(text)
|
||
|
return data
|
||
|
}
|
||
|
|
||
|
// diffRebuildtexts will use the patches to rebuild the text
|
||
|
func (vt *VersionedText) diffRebuildtexts(Diffs []diffmatchpatch.Diff) []string {
|
||
|
text := []string{"", ""}
|
||
|
for _, diff := range Diffs {
|
||
|
if diff.Type != diffmatchpatch.DiffInsert {
|
||
|
text[0] += diff.Text
|
||
|
}
|
||
|
if diff.Type != diffmatchpatch.DiffDelete {
|
||
|
text[1] += diff.Text
|
||
|
}
|
||
|
}
|
||
|
return text
|
||
|
}
|
||
|
|
||
|
// rebuildTextsToDiffN will use patches to build the text to a certain point
|
||
|
func (vt *VersionedText) rebuildTextsToDiffN(timestamp int64, snapshots []int64) (string, error) {
|
||
|
dmp := diffmatchpatch.New()
|
||
|
lastText := ""
|
||
|
|
||
|
for _, snapshot := range snapshots {
|
||
|
|
||
|
diff := vt.Diffs[snapshot]
|
||
|
seq1, _ := dmp.DiffFromDelta(lastText, diff)
|
||
|
textsLinemode := vt.diffRebuildtexts(seq1)
|
||
|
rebuilt := textsLinemode[len(textsLinemode)-1]
|
||
|
|
||
|
if snapshot == timestamp {
|
||
|
return rebuilt, nil
|
||
|
}
|
||
|
lastText = rebuilt
|
||
|
}
|
||
|
|
||
|
return "", fmt.Errorf("Could not rebuild from Diffs")
|
||
|
}
|
||
|
|
||
|
// GetCurrent returns the latest version
|
||
|
func (vt *VersionedText) GetCurrent() string {
|
||
|
return vt.CurrentText
|
||
|
}
|
||
|
|
||
|
// NumEdits returns the number of edits
|
||
|
func (vt *VersionedText) NumEdits() int {
|
||
|
return len(vt.Diffs)
|
||
|
}
|
||
|
|
||
|
// LastEditTime returns the last time it was edited
|
||
|
func (vt *VersionedText) LastEditTime() int64 {
|
||
|
max := int64(0)
|
||
|
for key := range vt.Diffs {
|
||
|
if key > max {
|
||
|
max = key
|
||
|
}
|
||
|
}
|
||
|
return max
|
||
|
}
|
||
|
|
||
|
// Update adds a new version to the current versions
|
||
|
func (vt *VersionedText) Update(newText string) {
|
||
|
// check for changes
|
||
|
if vt.CurrentText == newText {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
dmp := diffmatchpatch.New()
|
||
|
delta := dmp.DiffToDelta(dmp.DiffMain(vt.CurrentText, newText, true))
|
||
|
vt.CurrentText = newText
|
||
|
now := time.Now().UnixNano()
|
||
|
vt.Diffs[now] = delta
|
||
|
}
|
||
|
|
||
|
// GetSnapshots returns a sorted list of integers which
|
||
|
// represent timestamps for each snapshot
|
||
|
func (vt *VersionedText) GetSnapshots() []int64 {
|
||
|
keys := make([]int64, 0, len(vt.Diffs))
|
||
|
for k := range vt.Diffs {
|
||
|
keys = append(keys, k)
|
||
|
}
|
||
|
// SORT KEYS
|
||
|
keys = mergeSortInt64(keys)
|
||
|
return keys
|
||
|
}
|
||
|
|
||
|
// GetMajorSnapshots returns a sorted list of integers which
|
||
|
// represent timestamps for each snapshot, where snapshots
|
||
|
// must be at least the number of specified seconds apart.
|
||
|
func (vt *VersionedText) GetMajorSnapshots(seconds int64) []int64 {
|
||
|
keys := vt.GetSnapshots()
|
||
|
newKeys := make([]int64, len(keys))
|
||
|
newKeysI := 0
|
||
|
for i, key := range keys {
|
||
|
if i == 0 && len(keys) > 1 {
|
||
|
continue
|
||
|
}
|
||
|
if i == len(keys)-1 {
|
||
|
newKeys[newKeysI] = key
|
||
|
newKeysI++
|
||
|
continue
|
||
|
}
|
||
|
if key-keys[i-1] > seconds*1000000000 {
|
||
|
newKeys[newKeysI] = key
|
||
|
newKeysI++
|
||
|
}
|
||
|
}
|
||
|
return newKeys[0:newKeysI]
|
||
|
}
|
||
|
|
||
|
// GetMajorSnapshotsAndChangeSums returns the major snapshots timestamps and the changesums
|
||
|
// as two arrays
|
||
|
func (vt *VersionedText) GetMajorSnapshotsAndChangeSums(seconds int64) ([]int64, []int) {
|
||
|
keys := vt.GetSnapshots()
|
||
|
changeSums := vt.GetChangeSums()
|
||
|
|
||
|
majorChangeSums := make([]int, len(keys))
|
||
|
newKeys := make([]int64, len(keys))
|
||
|
newKeysI := 0
|
||
|
cumulativeSum := 0
|
||
|
for i, key := range keys {
|
||
|
cumulativeSum = cumulativeSum + changeSums[i]
|
||
|
if i == 0 && len(keys) > 1 {
|
||
|
continue
|
||
|
}
|
||
|
if i == len(keys)-1 {
|
||
|
newKeys[newKeysI] = key
|
||
|
majorChangeSums[newKeysI] = cumulativeSum
|
||
|
cumulativeSum = 0
|
||
|
newKeysI++
|
||
|
continue
|
||
|
}
|
||
|
if key-keys[i-1] > seconds*1000000000 {
|
||
|
majorChangeSums[newKeysI] = cumulativeSum
|
||
|
newKeys[newKeysI] = key
|
||
|
cumulativeSum = 0
|
||
|
newKeysI++
|
||
|
}
|
||
|
}
|
||
|
return newKeys[0:newKeysI], majorChangeSums[0:newKeysI]
|
||
|
}
|
||
|
|
||
|
// GetChangeSums returns the length of each change
|
||
|
func (vt *VersionedText) GetChangeSums() []int {
|
||
|
keys := vt.GetSnapshots()
|
||
|
changeSums := make([]int, len(keys))
|
||
|
for i, key := range keys {
|
||
|
changeSums[i] = 0
|
||
|
for _, val := range strings.Split(vt.Diffs[key], "\t") {
|
||
|
if val[0:1] == "-" {
|
||
|
a, _ := strconv.Atoi(val[1:])
|
||
|
changeSums[i] = changeSums[i] - a
|
||
|
} else if val[0:1] == "+" {
|
||
|
changeSums[i] = changeSums[i] + len(val[1:])
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return changeSums
|
||
|
}
|
||
|
|
||
|
// GetPreviousByTimestamp uses the diffs to rebuild to the specified snapshot
|
||
|
func (vt *VersionedText) GetPreviousByTimestamp(timestamp int64) (string, error) {
|
||
|
|
||
|
// check inputs
|
||
|
if 0 > timestamp {
|
||
|
return "", fmt.Errorf("Timestamps most be positive integer")
|
||
|
}
|
||
|
|
||
|
// get change snapshot
|
||
|
snapshots := vt.GetSnapshots()
|
||
|
|
||
|
// default to first value
|
||
|
ts := snapshots[0]
|
||
|
|
||
|
// find timestamp
|
||
|
for _, snapshot := range snapshots {
|
||
|
if timestamp >= snapshot && ts < snapshot {
|
||
|
ts = snapshot
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// use timestamp to find value
|
||
|
oldValue, err := vt.rebuildTextsToDiffN(ts, snapshots)
|
||
|
return oldValue, err
|
||
|
}
|
||
|
|
||
|
// GetPreviousByIndex returns a snapshot based on the index
|
||
|
func (vt *VersionedText) GetPreviousByIndex(idx int) (string, error) {
|
||
|
if vt.NumEdits() == 0 {
|
||
|
return vt.GetCurrent(), nil
|
||
|
}
|
||
|
// check inputs
|
||
|
if 0 > idx {
|
||
|
return "", fmt.Errorf("Index most be positive integer")
|
||
|
}
|
||
|
|
||
|
// get change snapshots
|
||
|
snapshots := vt.GetSnapshots()
|
||
|
|
||
|
// if index greater than length of snapshot
|
||
|
// default to last snapshot
|
||
|
if idx > len(snapshots)-1 {
|
||
|
idx = len(snapshots) - 1
|
||
|
}
|
||
|
|
||
|
// use index to find timestamp
|
||
|
ts := snapshots[idx]
|
||
|
|
||
|
// use timestamp to find value
|
||
|
oldValue, err := vt.rebuildTextsToDiffN(ts, snapshots)
|
||
|
return oldValue, err
|
||
|
}
|
||
|
|
||
|
func mergeInt64(l, r []int64) []int64 {
|
||
|
ret := make([]int64, 0, len(l)+len(r))
|
||
|
for len(l) > 0 || len(r) > 0 {
|
||
|
if len(l) == 0 {
|
||
|
return append(ret, r...)
|
||
|
}
|
||
|
if len(r) == 0 {
|
||
|
return append(ret, l...)
|
||
|
}
|
||
|
if l[0] <= r[0] {
|
||
|
ret = append(ret, l[0])
|
||
|
l = l[1:]
|
||
|
} else {
|
||
|
ret = append(ret, r[0])
|
||
|
r = r[1:]
|
||
|
}
|
||
|
}
|
||
|
return ret
|
||
|
}
|
||
|
|
||
|
func mergeSortInt64(s []int64) []int64 {
|
||
|
if len(s) <= 1 {
|
||
|
return s
|
||
|
}
|
||
|
n := len(s) / 2
|
||
|
l := mergeSortInt64(s[:n])
|
||
|
r := mergeSortInt64(s[n:])
|
||
|
return mergeInt64(l, r)
|
||
|
}
|