156 lines
3.8 KiB
Go
156 lines
3.8 KiB
Go
package migrator
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"io"
|
|
"sync/atomic"
|
|
|
|
log "github.com/go-pkgz/lgr"
|
|
"github.com/go-pkgz/syncs"
|
|
"github.com/pkg/errors"
|
|
|
|
"github.com/umputun/remark/backend/app/store"
|
|
"github.com/umputun/remark/backend/app/store/service"
|
|
)
|
|
|
|
const nativeVersion = 1
|
|
const defaultConcurrent = 8
|
|
|
|
// Native implements exporter and importer for internal store format
|
|
// {"version": 1, comments:[{...}\n,{}], meta: {meta}}
|
|
// each comments starts from the new line
|
|
type Native struct {
|
|
DataStore Store
|
|
Concurrent int
|
|
}
|
|
|
|
type meta struct {
|
|
Version int `json:"version"`
|
|
Users []service.UserMetaData `json:"users"`
|
|
Posts []service.PostMetaData `json:"posts"`
|
|
}
|
|
|
|
// Export all comments to writer as json strings. Each comment is one string, separated by "\n"
|
|
// The final file is a valid json
|
|
func (n *Native) Export(w io.Writer, siteID string) (size int, err error) {
|
|
|
|
if err = n.exportMeta(siteID, w); err != nil {
|
|
return 0, errors.Wrapf(err, "failed to export meta for site %s", siteID)
|
|
}
|
|
|
|
topics, err := n.DataStore.List(siteID, 0, 0)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
log.Printf("[DEBUG] exporting %d topics", len(topics))
|
|
commentsCount := 0
|
|
for i := len(topics) - 1; i >= 0; i-- { // topics from List sorted in opposite direction
|
|
topic := topics[i]
|
|
comments, e := n.DataStore.Find(store.Locator{SiteID: siteID, URL: topic.URL}, "time")
|
|
if e != nil {
|
|
return commentsCount, e
|
|
}
|
|
|
|
for _, comment := range comments {
|
|
|
|
buf := &bytes.Buffer{}
|
|
enc := json.NewEncoder(buf)
|
|
enc.SetEscapeHTML(false)
|
|
|
|
if err = enc.Encode(comment); err != nil {
|
|
return commentsCount, errors.Wrapf(err, "can't marshal %v", comments)
|
|
}
|
|
if _, err = w.Write(buf.Bytes()); err != nil {
|
|
return commentsCount, errors.Wrap(err, "can't write comment data")
|
|
}
|
|
commentsCount++
|
|
}
|
|
}
|
|
log.Printf("[DEBUG] exported %d comments", commentsCount)
|
|
return commentsCount, nil
|
|
}
|
|
|
|
// exportMeta appends user and post metas to exported stream
|
|
func (n *Native) exportMeta(siteID string, w io.Writer) (err error) {
|
|
m := meta{Version: nativeVersion}
|
|
m.Users, m.Posts, err = n.DataStore.Metas(siteID)
|
|
if err != nil {
|
|
return errors.Wrap(err, "can't get meta")
|
|
}
|
|
|
|
if err = json.NewEncoder(w).Encode(m); err != nil {
|
|
return errors.Wrap(err, "can't encode meta")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Import comments from json strings produced by Remark.Export
|
|
func (n *Native) Import(reader io.Reader, siteID string) (size int, err error) {
|
|
|
|
m := meta{}
|
|
dec := json.NewDecoder(reader)
|
|
if err = dec.Decode(&m); err != nil {
|
|
return 0, errors.Wrapf(err, "failed to import meta for site %s", siteID)
|
|
}
|
|
|
|
if m.Version != nativeVersion && m.Version != 0 { // this version allows back compatibility with 0 version
|
|
return 0, errors.Errorf("unexpected import file version %d", m.Version)
|
|
}
|
|
|
|
if err = n.DataStore.DeleteAll(siteID); err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
var failed, total, comments int64
|
|
|
|
concurrent := defaultConcurrent
|
|
if n.Concurrent > 0 {
|
|
concurrent = n.Concurrent
|
|
}
|
|
grp := syncs.NewSizedGroup(concurrent, syncs.Preemptive)
|
|
|
|
for {
|
|
comment := store.Comment{}
|
|
err = dec.Decode(&comment)
|
|
if err == io.EOF {
|
|
break
|
|
}
|
|
|
|
total++
|
|
|
|
if err != nil {
|
|
atomic.AddInt64(&failed, 1)
|
|
failed++
|
|
continue
|
|
}
|
|
|
|
// write comments in parallel
|
|
grp.Go(func(context.Context) {
|
|
if _, e := n.DataStore.Create(comment); e != nil {
|
|
atomic.AddInt64(&failed, 1)
|
|
log.Printf("[WARN] can't write %+v to store, %s", comment, e)
|
|
return
|
|
}
|
|
num := atomic.AddInt64(&comments, 1)
|
|
if num%1000 == 0 {
|
|
log.Printf("[DEBUG] imported %d comments", num)
|
|
}
|
|
})
|
|
|
|
}
|
|
|
|
grp.Wait()
|
|
|
|
if failed > 0 {
|
|
return int(comments), errors.Errorf("failed to save %d comments", failed)
|
|
}
|
|
log.Printf("[INFO] imported %d comments from %d records", comments, total)
|
|
|
|
err = n.DataStore.SetMetas(siteID, m.Users, m.Posts)
|
|
|
|
return int(comments), err
|
|
}
|