<commit_before>package repo

import (
	"encoding/base64"
	"fmt"
	"net/url"
	"reflect"
	"strings"
	"time"

	"github.com/flimzy/go-pouchdb"
	"github.com/flimzy/go-pouchdb/plugins/find"
	"github.com/gopherjs/gopherjs/js"
	"github.com/pborman/uuid"
	"github.com/pkg/errors"

	"github.com/FlashbackSRS/flashback-model"
	"github.com/FlashbackSRS/flashback/util"
)

// PouchDBOptions is passed to pouchdb.New(), and exists for the sake of automated
// tests. It should generally be ignored otherwise.
var PouchDBOptions pouchdb.Options

var couchHost string

func init() {
	couchHost = util.FindLink("flashbackdb")
}

// DB provides a simple wrapper around a pouchdb.DB object, complete with
// the pouchdb-find plugin
type DB struct {
	*pouchdb.PouchDB
	*find.PouchPluginFind
	User   *User
	DBName string
}

type dbInitFunc func(*DB) error

var initFuncs = map[string]dbInitFunc{
	"user":   userDBInit,
	"bundle": bundleDBInit,
}

// BundleDB returns a DB handle for the Bundle
func (u *User) BundleDB(b *fb.Bundle) (*DB, error) {
	return u.NewDB(b.ID.String())
}

// NewRemoteDB returns a DB handle to a remote (CouchDB) instance
func (u *User) NewRemoteDB(name string) (*DB, error) {
	return u.newDB(couchHost + "/" + name), nil
}

func (u *User) newDB(name string) *DB {
	pdb := pouchdb.NewWithOpts(name, PouchDBOptions)
	return &DB{
		PouchDB:         pdb,
		PouchPluginFind: find.New(pdb),
		DBName:          name,
		User:            u,
	}
}

// NewDB returns a DB handle, complete with binding to the Find plugin, to the
// requested DB
func (u *User) NewDB(name string) (*DB, error) {
	parts := strings.SplitN(name, "-", 2)
	if parts[0] != "user" {
		name = fmt.Sprintf("%s-%s-%s", parts[0], u.ID, parts[1])
	}
	db := u.newDB(name)
	return db, initDB(parts[0], db)
}

func initDB(name string, db *DB) error {
	if initFunc, ok := initFuncs[name]; ok {
		return errors.Wrap(initFunc(db), "db init")
	}
	return nil
}

// User provides a wrapper around fb.User
type User struct {
	*fb.User
}

// DB returns a DB handle to a User's DB
func (u *User) DB() (*DB, error) {
	return u.NewDB(u.ID.String())
}

// func (u *User) MasterReviewsDBName() string {
// 	return "reviews-" + u.ID.String()
// }
//
var currentUser *User

// CurrentUser returns a User object representing the currently logged in user, if any
func CurrentUser() (*User, error) {
	cookie := getCouchCookie(js.Global.Get("document").Get("cookie").String())
	userid := extractUserID(cookie)
	id := uuid.Parse(userid)
	if id == nil {
		return nil, errors.New("Invalid user ID found in cookie")
	}
	if currentUser != nil && currentUser.Equal(id) {
		return currentUser, nil
	}
	u, err := fb.NewUser(id, "")
	if err != nil {
		return nil, err
	}
	currentUser = &User{u}
	return currentUser, nil
}

func extractUserID(cookieValue string) string {
	decoded, _ := base64.StdEncoding.DecodeString(cookieValue)
	values := strings.Split(string(decoded), ":")
	return values[0]
}

func getCouchCookie(cookieHeader string) string {
	cookies := strings.Split(cookieHeader, ";")
	for _, cookie := range cookies {
		nv := strings.Split(strings.TrimSpace(cookie), "=")
		if nv[0] == "AuthSession" {
			value, _ := url.QueryUnescape(nv[1])
			return value
		}
	}
	return ""
}

// Compact compacts the requested DB
func (db *DB) Compact() error {
	return db.PouchDB.Compact(pouchdb.Options{})
}

// FlashbackDoc is a generic interface for all types of FB docs
type FlashbackDoc interface {
	DocID() string
	SetRev(string)
	ImportedTime() *time.Time
	ModifiedTime() *time.Time
	MergeImport(interface{}) (bool, error)
	UnmarshalJSON([]byte) error
}

// Save attempts to save any valid FlashbackDoc, including merging in case the
// document already exists from a previous import.
func (db *DB) Save(doc FlashbackDoc) error {<commit_msg>Add view cleanup to db maintenance method.<commit_after>package repo

import (
	"encoding/base64"
	"fmt"
	"net/url"
	"reflect"
	"strings"
	"time"

	"github.com/flimzy/go-pouchdb"
	"github.com/flimzy/go-pouchdb/plugins/find"
	"github.com/gopherjs/gopherjs/js"
	multierror "github.com/hashicorp/go-multierror"
	"github.com/pborman/uuid"
	"github.com/pkg/errors"

	"github.com/FlashbackSRS/flashback-model"
	"github.com/FlashbackSRS/flashback/util"
)

// PouchDBOptions is passed to pouchdb.New(), and exists for the sake of automated
// tests. It should generally be ignored otherwise.
var PouchDBOptions pouchdb.Options

var couchHost string

func init() {
	couchHost = util.FindLink("flashbackdb")
}

// DB provides a simple wrapper around a pouchdb.DB object, complete with
// the pouchdb-find plugin
type DB struct {
	*pouchdb.PouchDB
	*find.PouchPluginFind
	User   *User
	DBName string
}

type dbInitFunc func(*DB) error

var initFuncs = map[string]dbInitFunc{
	"user":   userDBInit,
	"bundle": bundleDBInit,
}

// BundleDB returns a DB handle for the Bundle
func (u *User) BundleDB(b *fb.Bundle) (*DB, error) {
	return u.NewDB(b.ID.String())
}

// NewRemoteDB returns a DB handle to a remote (CouchDB) instance
func (u *User) NewRemoteDB(name string) (*DB, error) {
	return u.newDB(couchHost + "/" + name), nil
}

func (u *User) newDB(name string) *DB {
	pdb := pouchdb.NewWithOpts(name, PouchDBOptions)
	return &DB{
		PouchDB:         pdb,
		PouchPluginFind: find.New(pdb),
		DBName:          name,
		User:            u,
	}
}

// NewDB returns a DB handle, complete with binding to the Find plugin, to the
// requested DB
func (u *User) NewDB(name string) (*DB, error) {
	parts := strings.SplitN(name, "-", 2)
	if parts[0] != "user" {
		name = fmt.Sprintf("%s-%s-%s", parts[0], u.ID, parts[1])
	}
	db := u.newDB(name)
	return db, initDB(parts[0], db)
}

func initDB(name string, db *DB) error {
	if initFunc, ok := initFuncs[name]; ok {
		return errors.Wrap(initFunc(db), "db init")
	}
	return nil
}

// User provides a wrapper around fb.User
type User struct {
	*fb.User
}

// DB returns a DB handle to a User's DB
func (u *User) DB() (*DB, error) {
	return u.NewDB(u.ID.String())
}

// func (u *User) MasterReviewsDBName() string {
// 	return "reviews-" + u.ID.String()
// }
//
var currentUser *User

// CurrentUser returns a User object representing the currently logged in user, if any
func CurrentUser() (*User, error) {
	cookie := getCouchCookie(js.Global.Get("document").Get("cookie").String())
	userid := extractUserID(cookie)
	id := uuid.Parse(userid)
	if id == nil {
		return nil, errors.New("Invalid user ID found in cookie")
	}
	if currentUser != nil && currentUser.Equal(id) {
		return currentUser, nil
	}
	u, err := fb.NewUser(id, "")
	if err != nil {
		return nil, err
	}
	currentUser = &User{u}
	return currentUser, nil
}

func extractUserID(cookieValue string) string {
	decoded, _ := base64.StdEncoding.DecodeString(cookieValue)
	values := strings.Split(string(decoded), ":")
	return values[0]
}

func getCouchCookie(cookieHeader string) string {
	cookies := strings.Split(cookieHeader, ";")
	for _, cookie := range cookies {
		nv := strings.Split(strings.TrimSpace(cookie), "=")
		if nv[0] == "AuthSession" {
			value, _ := url.QueryUnescape(nv[1])
			return value
		}
	}
	return ""
}

// Compact compacts the requested DB
func (db *DB) Compact() error {
	var errs error
	if err := db.PouchDB.Compact(pouchdb.Options{}); err != nil {
		errs = multierror.Append(errs, err)
	}
	if err := db.PouchDB.ViewCleanup(); err != nil {
		errs = multierror.Append(errs, err)
	}
	return errs
}

// FlashbackDoc is a generic interface for all types of FB docs
type FlashbackDoc interface {
	DocID() string
	SetRev(string)
	ImportedTime() *time.Time
	ModifiedTime() *time.Time
	MergeImport(interface{}) (bool, error)
	UnmarshalJSON([]byte) error
}

// Save attempts to save any valid FlashbackDoc, including merging in case the
// document already exists from a previous import.
func (db *DB) Save(doc FlashbackDoc) error {
{'fraction_non_alphanumeric': 0.13998463114754098, 'fraction_numerical': 0.0030737704918032786, 'mean_word_length': 4.002562459961563, 'pattern_counts': {'":': 6, '<': 3, '<?xml version=': 0, '>': 3, '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': '888965', 'n_tokens_mistral': 2838, 'n_tokens_neox': 2757, 'n_words': 785}