<commit_before>package main

import (
	"fmt"
	"log"
	"net/http"
	"net/url"
	"os"
	"strings"
)

var (
	AppURL  string
	IconURL string
	MemeURL string
	DEBUG   bool
)

const (
	iconPath       = "static/icon.png"
	memePath       = "static/spongemock.jpg"
	groupThreshold = 0.8
)

type EnvVariable struct {
	Name     string
	Variable *string
}

type WebPlugin interface {
	Name() string
	EnvVariables() []EnvVariable
	RegisterHandles(*http.ServeMux)
}

func init() {
	SetEnvVariable("APP_URL", &AppURL)

	u, err := url.Parse(AppURL)
	if err != nil {
		log.Fatal("invalid $APP_URL %s", AppURL)
	}
	icon, _ := url.Parse(iconPath)
	IconURL = u.ResolveReference(icon).String()
	meme, _ := url.Parse(memePath)
	MemeURL = u.ResolveReference(meme).String()

	DEBUG = strings.ToLower(os.Getenv("DEBUG")) != "false"
	if DEBUG {
		log.Println("In DEBUG mode")
	}
}

func SetEnvVariable(name string, value *string) {
	*value = os.Getenv(name)
	if *value == "" {
		log.Fatal(fmt.Errorf("$%s must be set!", name))
	}
}

func (v EnvVariable) Set() {
	SetEnvVariable(v.Name, v.Variable)
}
<commit_msg>Add DB initialization to the web dyno<commit_after>package main

import (
	"database/sql"
	"fmt"
	"log"
	"net/http"
	"net/url"
	"os"
	"strings"

	_ "github.com/lib/pq"
)

var (
	AppURL  string
	IconURL string
	MemeURL string
	DB      *sql.DB
	DEBUG   bool
)

const (
	iconPath       = "static/icon.png"
	memePath       = "static/spongemock.jpg"
	groupThreshold = 0.8
)

type EnvVariable struct {
	Name     string
	Variable *string
}

type WebPlugin interface {
	Name() string
	EnvVariables() []EnvVariable
	RegisterHandles(*http.ServeMux)
}

func init() {
	SetEnvVariable("APP_URL", &AppURL)

	u, err := url.Parse(AppURL)
	if err != nil {
		log.Fatal("invalid $APP_URL %s", AppURL)
	}
	icon, _ := url.Parse(iconPath)
	IconURL = u.ResolveReference(icon).String()
	meme, _ := url.Parse(memePath)
	MemeURL = u.ResolveReference(meme).String()

	dbURL := os.Getenv("DATABASE_URL")
	if dbURL != "" {
		DB, err = sql.Open("postgres", dbURL)
		if err != nil {
			log.Println("DATABASE_URL is not set")
			DB = nil
		} else if err = DB.Ping(); err != nil {
			log.Println("error pinging the database:", err)
			log.Println("closing database connection:", DB.Close())
			DB = nil
		}
	}

	DEBUG = strings.ToLower(os.Getenv("DEBUG")) != "false"
	if DEBUG {
		log.Println("In DEBUG mode")
	}
}

func SetEnvVariable(name string, value *string) {
	*value = os.Getenv(name)
	if *value == "" {
		log.Fatal(fmt.Errorf("$%s must be set!", name))
	}
}

func (v EnvVariable) Set() {
	SetEnvVariable(v.Name, v.Variable)
}
<|endoftext|>


<commit_before>package main

import (
	"errors"
	"flag"
	"fmt"
	"os"
	"os/exec"
	"os/signal"
	"path"
	"path/filepath"
	"runtime"
	"strings"
	"syscall"
	"time"

	"bazil.org/fuse"
	"bazil.org/fuse/fs"
	_ "bazil.org/fuse/fs/fstestutil"
	"github.com/burkemw3/syncthing-fuse/lib/model"
	"github.com/syncthing/protocol"
	"golang.org/x/net/context"
)

var Usage = func() {
	fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0])
	fmt.Fprintf(os.Stderr, "  %s MOUNTPOINT\n", os.Args[0])
	flag.PrintDefaults()
}

func MountFuse(mountpoint string, m *model.Model) {
	c, err := fuse.Mount(
		mountpoint,
		fuse.FSName("syncthingfuse"),
		fuse.Subtype("syncthingfuse"),
		fuse.LocalVolume(),
		fuse.VolumeName("Syncthing FUSE"),
	)
	if err != nil {
		l.Warnln(err)
	}

	sigc := make(chan os.Signal, 1)
	signal.Notify(sigc, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT)

	doneServe := make(chan error, 1)
	go func() {
		doneServe <- fs.Serve(c, FS{m: m})
	}()

	select {
	case err := <-doneServe:
		l.Infoln("conn.Serve returned %v", err)

		// check if the mount process has an error to report
		<-c.Ready
		if err := c.MountError; err != nil {
			l.Warnln("conn.MountError: %v", err)
		}
	case sig := <-sigc:
		l.Infoln("Signal %s received, shutting down.", sig)
	}

	time.AfterFunc(3*time.Second, func() {
		os.Exit(1)
	})
	l.Infoln("Unmounting...")
	err = Unmount(mountpoint)
	l.Infoln("Unmount = %v", err)

	l.Infoln("syncthing FUSE process ending.")
}

var (
	folder    = "syncthingfusetest"
	debugFuse = strings.Contains(os.Getenv("STTRACE"), "fuse") || os.Getenv("STTRACE") == "all"
)

func makeModel() *model.Model {
	m := model.NewModel()

	deviceID := protocol.DeviceID{}
	flags := uint32(0)
	options := []protocol.Option{}

	files := []protocol.FileInfo{
		protocol.FileInfo{Name: "file1"},
		protocol.FileInfo{Name: "file2"},
		protocol.FileInfo{Name: "dir1", Flags: protocol.FlagDirectory},
		protocol.FileInfo{Name: "dir1/dirfile1"},
		protocol.FileInfo{Name: "dir1/dirfile2"},
	}

	m.Index(deviceID, folder, files, flags, options)

	return m
}

type FS struct {
	m *model.Model
}

func (fs FS) Root() (fs.Node, error) {
	if debugFuse {
		l.Debugln("Root")
	}
	return Dir{m: fs.m}, nil
}

// Dir implements both Node and Handle for the root directory.
type Dir struct {
	path string
	m    *model.Model
}

func (d Dir) Attr(ctx context.Context, a *fuse.Attr) error {
	if debugFuse {
		l.Debugln("Dir Attr")
	}
	a.Mode = os.ModeDir | 0555
	return nil
}

func (d Dir) Lookup(ctx context.Context, name string) (fs.Node, error) {
	if debugFuse {
		l.Debugln("Dir %s Lookup for %s", d.path, name)
	}
	entry := d.m.GetEntry(folder, filepath.Join(d.path, name))

	var node fs.Node
	if entry.IsDirectory() {
		node = Dir{
			path: entry.Name,
			m:    d.m,
		}
	} else {
		node = File{
			path: entry.Name,
			m:    d.m,
		}
	}

	return node, nil
}

func (d Dir) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) {
	if debugFuse {
		l.Debugln("ReadDirAll %s", d.path)
	}

	p := path.Clean(d.path)

	entries := d.m.GetChildren(folder, p)
	result := make([]fuse.Dirent, len(entries))
	for i, entry := range entries {
		eType := fuse.DT_File
		if entry.IsDirectory() {
			eType = fuse.DT_Dir
		}
		result[i] = fuse.Dirent{
			Name: path.Base(entry.Name),
			Type: eType,
		}
	}

	return result, nil
}

// File implements both Node and Handle for the hello file.
type File struct {
	path string
	m    *model.Model
}

func (f File) Attr(ctx context.Context, a *fuse.Attr) error {
	entry := f.m.GetEntry(folder, f.path)

	a.Mode = 0444
	a.Mtime = time.Now()
	a.Size = uint64(entry.Size())
	return nil
}

func (f File) ReadAll(ctx context.Context) ([]byte, error) {
	data, err := f.m.GetFileData(folder, f.path)

	return data, err
}

// Unmount attempts to unmount the provided FUSE mount point, forcibly
// if necessary.
func Unmount(point string) error {
	var cmd *exec.Cmd
	switch runtime.GOOS {
	case "darwin":
		cmd = exec.Command("/usr/sbin/diskutil", "umount", "force", point)
	case "linux":
		cmd = exec.Command("fusermount", "-u", point)
	default:
		return errors.New("unmount: unimplemented")
	}

	errc := make(chan error, 1)
	go func() {
		if err := exec.Command("umount", point).Run(); err == nil {
			errc <- err
		}
		// retry to unmount with the fallback cmd
		errc <- cmd.Run()
	}()
	select {
	case <-time.After(1 * time.Second):
		return errors.New("umount timeout")
	case err := <-errc:
		return err
	}
}
<commit_msg>Use syncthing modified as file modified datetime<commit_after>package main

import (
	"errors"
	"flag"
	"fmt"
	"os"
	"os/exec"
	"os/signal"
	"path"
	"path/filepath"
	"runtime"
	"strings"
	"syscall"
	"time"

	"bazil.org/fuse"
	"bazil.org/fuse/fs"
	_ "bazil.org/fuse/fs/fstestutil"
	"github.com/burkemw3/syncthing-fuse/lib/model"
	"github.com/syncthing/protocol"
	"golang.org/x/net/context"
)

var Usage = func() {
	fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0])
	fmt.Fprintf(os.Stderr, "  %s MOUNTPOINT\n", os.Args[0])
	flag.PrintDefaults()
}

func MountFuse(mountpoint string, m *model.Model) {
	c, err := fuse.Mount(
		mountpoint,
		fuse.FSName("syncthingfuse"),
		fuse.Subtype("syncthingfuse"),
		fuse.LocalVolume(),
		fuse.VolumeName("Syncthing FUSE"),
	)
	if err != nil {
		l.Warnln(err)
	}

	sigc := make(chan os.Signal, 1)
	signal.Notify(sigc, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT)

	doneServe := make(chan error, 1)
	go func() {
		doneServe <- fs.Serve(c, FS{m: m})
	}()

	select {
	case err := <-doneServe:
		l.Infoln("conn.Serve returned %v", err)

		// check if the mount process has an error to report
		<-c.Ready
		if err := c.MountError; err != nil {
			l.Warnln("conn.MountError: %v", err)
		}
	case sig := <-sigc:
		l.Infoln("Signal %s received, shutting down.", sig)
	}

	time.AfterFunc(3*time.Second, func() {
		os.Exit(1)
	})
	l.Infoln("Unmounting...")
	err = Unmount(mountpoint)
	l.Infoln("Unmount = %v", err)

	l.Infoln("syncthing FUSE process ending.")
}

var (
	folder    = "syncthingfusetest"
	debugFuse = strings.Contains(os.Getenv("STTRACE"), "fuse") || os.Getenv("STTRACE") == "all"
)

func makeModel() *model.Model {
	m := model.NewModel()

	deviceID := protocol.DeviceID{}
	flags := uint32(0)
	options := []protocol.Option{}

	files := []protocol.FileInfo{
		protocol.FileInfo{Name: "file1"},
		protocol.FileInfo{Name: "file2"},
		protocol.FileInfo{Name: "dir1", Flags: protocol.FlagDirectory},
		protocol.FileInfo{Name: "dir1/dirfile1"},
		protocol.FileInfo{Name: "dir1/dirfile2"},
	}

	m.Index(deviceID, folder, files, flags, options)

	return m
}

type FS struct {
	m *model.Model
}

func (fs FS) Root() (fs.Node, error) {
	if debugFuse {
		l.Debugln("Root")
	}
	return Dir{m: fs.m}, nil
}

// Dir implements both Node and Handle for the root directory.
type Dir struct {
	path string
	m    *model.Model
}

func (d Dir) Attr(ctx context.Context, a *fuse.Attr) error {
	if debugFuse {
		l.Debugln("Dir Attr")
	}
	entry := d.m.GetEntry(folder, d.path)
	a.Mode = os.ModeDir | 0555
	a.Mtime = time.Unix(entry.Modified, 0)
	return nil
}

func (d Dir) Lookup(ctx context.Context, name string) (fs.Node, error) {
	if debugFuse {
		l.Debugln("Dir %s Lookup for %s", d.path, name)
	}
	entry := d.m.GetEntry(folder, filepath.Join(d.path, name))

	var node fs.Node
	if entry.IsDirectory() {
		node = Dir{
			path: entry.Name,
			m:    d.m,
		}
	} else {
		node = File{
			path: entry.Name,
			m:    d.m,
		}
	}

	return node, nil
}

func (d Dir) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) {
	if debugFuse {
		l.Debugln("ReadDirAll %s", d.path)
	}

	p := path.Clean(d.path)

	entries := d.m.GetChildren(folder, p)
	result := make([]fuse.Dirent, len(entries))
	for i, entry := range entries {
		eType := fuse.DT_File
		if entry.IsDirectory() {
			eType = fuse.DT_Dir
		}
		result[i] = fuse.Dirent{
			Name: path.Base(entry.Name),
			Type: eType,
		}
	}

	return result, nil
}

// File implements both Node and Handle for the hello file.
type File struct {
	path string
	m    *model.Model
}

func (f File) Attr(ctx context.Context, a *fuse.Attr) error {
	entry := f.m.GetEntry(folder, f.path)

	a.Mode = 0444
	a.Mtime = time.Unix(entry.Modified, 0)
	a.Size = uint64(entry.Size())
	return nil
}

func (f File) ReadAll(ctx context.Context) ([]byte, error) {
	data, err := f.m.GetFileData(folder, f.path)

	return data, err
}

// Unmount attempts to unmount the provided FUSE mount point, forcibly
// if necessary.
func Unmount(point string) error {
	var cmd *exec.Cmd
	switch runtime.GOOS {
	case "darwin":
		cmd = exec.Command("/usr/sbin/diskutil", "umount", "force", point)
	case "linux":
		cmd = exec.Command("fusermount", "-u", point)
	default:
		return errors.New("unmount: unimplemented")
	}

	errc := make(chan error, 1)
	go func() {
		if err := exec.Command("umount", point).Run(); err == nil {
			errc <- err
		}
		// retry to unmount with the fallback cmd
		errc <- cmd.Run()
	}()
	select {
	case <-time.After(1 * time.Second):
		return errors.New("umount timeout")
	case err := <-errc:
		return err
	}
}
<|endoftext|>


<commit_before>// Copyright 2016 The Upspin Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
	"bytes"
	"flag"
	"fmt"
	"html/template"
	"io/ioutil"
	"os"
	"path/filepath"

	yaml "gopkg.in/yaml.v2"

	"upspin.io/config"
	"upspin.io/flags"
	"upspin.io/upspin"
)

// This file implements the initial configuration for a new domain.

func (s *State) setupdomain(args ...string) {
	const (
		help = `
Setupdomain is the first step in setting up an upspinserver.
The next steps are 'setupstorage' and 'setupserver'.

It generates keys and config files for Upspin server users, placing them in
$where/$domain (the values of the -where and -domain flags substitute for
$where and $domain respectively) and generates a signature that proves that the
calling Upspin user has control over domain.

If the -cluster flag is specified, keys for upspin-dir@domain and
upspin-store@domain are created instead. This flag should be used when setting
up a domain that will run its directory and store servers separately, requiring
separate users to adminster each one. When -cluster is not specified, keys for
a single user (upspin@domain) are generated.

If any state exists at the given location (-where) then the command aborts.
`
	)
	fs := flag.NewFlagSet("setupdomain", flag.ExitOnError)
	where := fs.String("where", filepath.Join(os.Getenv("HOME"), "upspin", "deploy"), "`directory` to store private configuration files")
	domain := fs.String("domain", "", "domain `name` for this Upspin installation")
	curveName := fs.String("curve", "p256", "cryptographic curve `name`: p256, p384, or p521")
	putUsers := fs.Bool("put-users", false, "put server users to the key server")
	cluster := fs.Bool("cluster", false, "generate keys for upspin-dir@domain and upspin-store@domain (default is upspin@domain only)")
	s.parseFlags(fs, args, help, "setupdomain [-where=$HOME/upspin/deploy] [-cluster] -domain=<name>")
	if *where == "" {
		s.failf("the -where flag must not be empty")
		fs.Usage()
	}
	if *domain == "" {
		s.failf("the -domain flag must be provided")
		fs.Usage()
	}
	switch *curveName {
	case "p256", "p384", "p521":
		// OK
	default:
		s.exitf("no such curve %q", *curveName)
	}

	if !*cluster {
		if *putUsers {
			s.exitf("the -put-users flag requires -cluster")
		}
		s.setuphost(*where, *domain, *curveName)
		return
	}

	var (
		dirServerPath   = filepath.Join(*where, *domain, "dirserver")
		storeServerPath = filepath.Join(*where, *domain, "storeserver")
		dirConfig       = filepath.Join(dirServerPath, "config")
		storeConfig     = filepath.Join(storeServerPath, "config")
	)

	if *putUsers {
		dirFile, dirUser, err := writeUserFile(dirConfig)
		if err != nil {
			s.exit(err)
		}
		storeFile, storeUser, err := writeUserFile(storeConfig)
		if err != nil {
			s.exit(err)
		}
		s.user("-put", "-in", dirFile)
		os.Remove(dirFile)
		s.user("-put", "-in", storeFile)
		os.Remove(storeFile)
		fmt.Printf("Successfully put %q and %q to the key server.\n", dirUser, storeUser)
		return
	}

	s.shouldNotExist(dirServerPath)
	s.shouldNotExist(storeServerPath)
	s.mkdirAllLocal(dirServerPath)
	s.mkdirAllLocal(storeServerPath)

	// Generate keys for the dirserver and the storeserver.
	var noProquint string
	dirPublic, dirPrivate, _, err := createKeys(*curveName, noProquint)
	if err != nil {
		s.exit(err)
	}
	storePublic, storePrivate, _, err := createKeys(*curveName, noProquint)
	if err != nil {
		s.exit(err)
	}
	err = writeKeys(dirServerPath, dirPublic, dirPrivate)
	if err != nil {
		s.exit(err)
	}
	err = writeKeys(storeServerPath, storePublic, storePrivate)
	if err != nil {
		s.exit(err)
	}

	// Generate config files for those users.
	dirEndpoint := upspin.Endpoint{
		Transport: upspin.Remote,
		NetAddr:   upspin.NetAddr("dir." + *domain + ":443"),
	}
	storeEndpoint := upspin.Endpoint{
		Transport: upspin.Remote,
		NetAddr:   upspin.NetAddr("store." + *domain + ":443"),
	}
	var dirBody bytes.Buffer
	if err := configTemplate.Execute(&dirBody, configData{
		UserName:  upspin.UserName("upspin-dir@" + *domain),
		Store:     &storeEndpoint,
		Dir:       &dirEndpoint,
		SecretDir: dirServerPath,
		Packing:   "ee",
	}); err != nil {
		s.exit(err)
	}
	if err := ioutil.WriteFile(dirConfig, dirBody.Bytes(), 0644); err != nil {
		s.exit(err)
	}
	var storeBody bytes.Buffer
	if err := configTemplate.Execute(&storeBody, configData{
		UserName:  upspin.UserName("upspin-store@" + *domain),
		Store:     &storeEndpoint,
		Dir:       &dirEndpoint,
		SecretDir: storeServerPath,
		Packing:   "plain",
	}); err != nil {
		s.exit(err)
	}
	if err := ioutil.WriteFile(storeConfig, storeBody.Bytes(), 0644); err != nil {
		s.exit(err)
	}

	// Generate signature.
	msg := "upspin-domain:" + *domain + "-" + string(s.config.UserName())
	sig, err := s.config.Factotum().Sign([]byte(msg))
	if err != nil {
		s.exit(err)
	}

	err = setupDomainTemplate.Execute(os.Stdout, setupDomainData{
		Dir:       filepath.Join(*where, flags.Project),
		Where:     *where,
		Domain:    *domain,
		Project:   flags.Project,
		UserName:  template.HTML(s.config.UserName()),
		Signature: fmt.Sprintf("%x-%x", sig.R, sig.S),
	})
	if err != nil {
		s.exit(err)
	}
}

type setupDomainData struct {
	Dir, Where string
	Domain     string
	Project    string
	// template.HTML is used instead of upspin.UserName because html/template
	// escapes characters like +.
	// TODO: Figure out and document why html/template is used instead of
	// text/template.
	UserName  template.HTML
	Signature string
}

var setupDomainTemplate = template.Must(template.New("setupdomain").Parse(`
Keys and config files for the users
	upspin-dir@{{.Domain}}
	upspin-store@{{.Domain}}
were generated and placed under the directory:
	{{.Dir}}

To prove that {{.UserName}} is the owner of {{.Domain}},
add the following record to {{.Domain}}'s DNS zone:

	NAME	TYPE	TTL	DATA
	@	TXT	15m	upspin:{{.Signature}}

Once the DNS change propagates the key server will use the TXT record to verify
that {{.UserName}} is authorized to register users under {{.Domain}}.
To register the users listed above, run this command:

	$ upspin -project={{.Project}} setupdomain -where={{.Where}} -put-users {{.Domain}}

`))

// writeUserFile reads the specified config file and writes a YAML-encoded
// upspin.User to userFile. It also returns the username.
func writeUserFile(configFile string) (userFile string, u upspin.UserName, err error) {
	cfg, err := config.FromFile(configFile)
	if err != nil {
		return "", "", err
	}
	b, err := yaml.Marshal(config.User(cfg))
	if err != nil {
		return "", "", err
	}
	f, err := ioutil.TempFile("", "setupdomain-user")
	if err != nil {
		return "", "", err
	}
	if _, err := f.Write(b); err != nil {
		os.Remove(f.Name())
		return "", "", err
	}
	if err := f.Close(); err != nil {
		os.Remove(f.Name())
		return "", "", err
	}
	return f.Name(), cfg.UserName(), nil
}

func (s *State) setuphost(where, domain, curve string) {
	cfgPath := filepath.Join(where, domain)
	s.shouldNotExist(cfgPath)
	s.mkdirAllLocal(cfgPath)

	// Generate and write keys for the server user.
	var noProquint string
	pub, pri, _, err := createKeys(curve, noProquint)
	if err != nil {
		s.exit(err)
	}
	err = writeKeys(cfgPath, pub, pri)
	if err != nil {
		s.exit(err)
	}

	// Generate signature.
	msg := "upspin-domain:" + domain + "-" + string(s.config.UserName())
	sig, err := s.config.Factotum().Sign([]byte(msg))
	if err != nil {
		s.exit(err)
	}

	// Write server config file.
	s.writeServerConfig(cfgPath, &ServerConfig{
		User: upspin.UserName("upspin@" + domain),
	})

	err = setupHostTemplate.Execute(os.Stdout, setupDomainData{
		Dir:       cfgPath,
		Where:     where,
		Domain:    domain,
		Project:   flags.Project,
		UserName:  template.HTML(s.config.UserName()),
		Signature: fmt.Sprintf("%x-%x", sig.R, sig.S),
	})
	if err != nil {
		s.exit(err)
	}
}

var setupHostTemplate = template.Must(template.New("setuphost").Parse(`
Domain configuration and keys for the user
	upspin@{{.Domain}}
were generated and placed under the directory:
	{{.Dir}}

To prove that {{.UserName}} is the owner of {{.Domain}},
add the following record to {{.Domain}}'s DNS zone:

	NAME	TYPE	TTL	DATA
	@	TXT	15m	upspin:{{.Signature}}

Once the DNS change propagates the key server will use the TXT record to verify
that {{.UserName}} is authorized to register users under {{.Domain}}.

After that, the next step is to run 'upspin setupstorage'.
`))
<commit_msg>cmd/upspin: use text/template instead of html/template<commit_after>// Copyright 2016 The Upspin Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
	"bytes"
	"flag"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"text/template"

	yaml "gopkg.in/yaml.v2"

	"upspin.io/config"
	"upspin.io/flags"
	"upspin.io/upspin"
)

// This file implements the initial configuration for a new domain.

func (s *State) setupdomain(args ...string) {
	const (
		help = `
Setupdomain is the first step in setting up an upspinserver.
The next steps are 'setupstorage' and 'setupserver'.

It generates keys and config files for Upspin server users, placing them in
$where/$domain (the values of the -where and -domain flags substitute for
$where and $domain respectively) and generates a signature that proves that the
calling Upspin user has control over domain.

If the -cluster flag is specified, keys for upspin-dir@domain and
upspin-store@domain are created instead. This flag should be used when setting
up a domain that will run its directory and store servers separately, requiring
separate users to adminster each one. When -cluster is not specified, keys for
a single user (upspin@domain) are generated.

If any state exists at the given location (-where) then the command aborts.
`
	)
	fs := flag.NewFlagSet("setupdomain", flag.ExitOnError)
	where := fs.String("where", filepath.Join(os.Getenv("HOME"), "upspin", "deploy"), "`directory` to store private configuration files")
	domain := fs.String("domain", "", "domain `name` for this Upspin installation")
	curveName := fs.String("curve", "p256", "cryptographic curve `name`: p256, p384, or p521")
	putUsers := fs.Bool("put-users", false, "put server users to the key server")
	cluster := fs.Bool("cluster", false, "generate keys for upspin-dir@domain and upspin-store@domain (default is upspin@domain only)")
	s.parseFlags(fs, args, help, "setupdomain [-where=$HOME/upspin/deploy] [-cluster] -domain=<name>")
	if *where == "" {
		s.failf("the -where flag must not be empty")
		fs.Usage()
	}
	if *domain == "" {
		s.failf("the -domain flag must be provided")
		fs.Usage()
	}
	switch *curveName {
	case "p256", "p384", "p521":
		// OK
	default:
		s.exitf("no such curve %q", *curveName)
	}

	if !*cluster {
		if *putUsers {
			s.exitf("the -put-users flag requires -cluster")
		}
		s.setuphost(*where, *domain, *curveName)
		return
	}

	var (
		dirServerPath   = filepath.Join(*where, *domain, "dirserver")
		storeServerPath = filepath.Join(*where, *domain, "storeserver")
		dirConfig       = filepath.Join(dirServerPath, "config")
		storeConfig     = filepath.Join(storeServerPath, "config")
	)

	if *putUsers {
		dirFile, dirUser, err := writeUserFile(dirConfig)
		if err != nil {
			s.exit(err)
		}
		storeFile, storeUser, err := writeUserFile(storeConfig)
		if err != nil {
			s.exit(err)
		}
		s.user("-put", "-in", dirFile)
		os.Remove(dirFile)
		s.user("-put", "-in", storeFile)
		os.Remove(storeFile)
		fmt.Printf("Successfully put %q and %q to the key server.\n", dirUser, storeUser)
		return
	}

	s.shouldNotExist(dirServerPath)
	s.shouldNotExist(storeServerPath)
	s.mkdirAllLocal(dirServerPath)
	s.mkdirAllLocal(storeServerPath)

	// Generate keys for the dirserver and the storeserver.
	var noProquint string
	dirPublic, dirPrivate, _, err := createKeys(*curveName, noProquint)
	if err != nil {
		s.exit(err)
	}
	storePublic, storePrivate, _, err := createKeys(*curveName, noProquint)
	if err != nil {
		s.exit(err)
	}
	err = writeKeys(dirServerPath, dirPublic, dirPrivate)
	if err != nil {
		s.exit(err)
	}
	err = writeKeys(storeServerPath, storePublic, storePrivate)
	if err != nil {
		s.exit(err)
	}

	// Generate config files for those users.
	dirEndpoint := upspin.Endpoint{
		Transport: upspin.Remote,
		NetAddr:   upspin.NetAddr("dir." + *domain + ":443"),
	}
	storeEndpoint := upspin.Endpoint{
		Transport: upspin.Remote,
		NetAddr:   upspin.NetAddr("store." + *domain + ":443"),
	}
	var dirBody bytes.Buffer
	if err := configTemplate.Execute(&dirBody, configData{
		UserName:  upspin.UserName("upspin-dir@" + *domain),
		Store:     &storeEndpoint,
		Dir:       &dirEndpoint,
		SecretDir: dirServerPath,
		Packing:   "ee",
	}); err != nil {
		s.exit(err)
	}
	if err := ioutil.WriteFile(dirConfig, dirBody.Bytes(), 0644); err != nil {
		s.exit(err)
	}
	var storeBody bytes.Buffer
	if err := configTemplate.Execute(&storeBody, configData{
		UserName:  upspin.UserName("upspin-store@" + *domain),
		Store:     &storeEndpoint,
		Dir:       &dirEndpoint,
		SecretDir: storeServerPath,
		Packing:   "plain",
	}); err != nil {
		s.exit(err)
	}
	if err := ioutil.WriteFile(storeConfig, storeBody.Bytes(), 0644); err != nil {
		s.exit(err)
	}

	// Generate signature.
	msg := "upspin-domain:" + *domain + "-" + string(s.config.UserName())
	sig, err := s.config.Factotum().Sign([]byte(msg))
	if err != nil {
		s.exit(err)
	}

	err = setupDomainTemplate.Execute(os.Stdout, setupDomainData{
		Dir:       filepath.Join(*where, flags.Project),
		Where:     *where,
		Domain:    *domain,
		Project:   flags.Project,
		UserName:  s.config.UserName(),
		Signature: fmt.Sprintf("%x-%x", sig.R, sig.S),
	})
	if err != nil {
		s.exit(err)
	}
}

type setupDomainData struct {
	Dir, Where string
	Domain     string
	Project    string
	UserName   upspin.UserName
	Signature  string
}

var setupDomainTemplate = template.Must(template.New("setupdomain").Parse(`
Keys and config files for the users
	upspin-dir@{{.Domain}}
	upspin-store@{{.Domain}}
were generated and placed under the directory:
	{{.Dir}}

To prove that {{.UserName}} is the owner of {{.Domain}},
add the following record to {{.Domain}}'s DNS zone:

	NAME	TYPE	TTL	DATA
	@	TXT	15m	upspin:{{.Signature}}

Once the DNS change propagates the key server will use the TXT record to verify
that {{.UserName}} is authorized to register users under {{.Domain}}.
To register the users listed above, run this command:

	$ upspin -project={{.Project}} setupdomain -where={{.Where}} -put-users {{.Domain}}

`))

// writeUserFile reads the specified config file and writes a YAML-encoded
// upspin.User to userFile. It also returns the username.
func writeUserFile(configFile string) (userFile string, u upspin.UserName, err error) {
	cfg, err := config.FromFile(configFile)
	if err != nil {
		return "", "", err
	}
	b, err := yaml.Marshal(config.User(cfg))
	if err != nil {
		return "", "", err
	}
	f, err := ioutil.TempFile("", "setupdomain-user")
	if err != nil {
		return "", "", err
	}
	if _, err := f.Write(b); err != nil {
		os.Remove(f.Name())
		return "", "", err
	}
	if err := f.Close(); err != nil {
		os.Remove(f.Name())
		return "", "", err
	}
	return f.Name(), cfg.UserName(), nil
}

func (s *State) setuphost(where, domain, curve string) {
	cfgPath := filepath.Join(where, domain)
	s.shouldNotExist(cfgPath)
	s.mkdirAllLocal(cfgPath)

	// Generate and write keys for the server user.
	var noProquint string
	pub, pri, _, err := createKeys(curve, noProquint)
	if err != nil {
		s.exit(err)
	}
	err = writeKeys(cfgPath, pub, pri)
	if err != nil {
		s.exit(err)
	}

	// Generate signature.
	msg := "upspin-domain:" + domain + "-" + string(s.config.UserName())
	sig, err := s.config.Factotum().Sign([]byte(msg))
	if err != nil {
		s.exit(err)
	}

	// Write server config file.
	s.writeServerConfig(cfgPath, &ServerConfig{
		User: upspin.UserName("upspin@" + domain),
	})

	err = setupHostTemplate.Execute(os.Stdout, setupDomainData{
		Dir:       cfgPath,
		Where:     where,
		Domain:    domain,
		Project:   flags.Project,
		UserName:  s.config.UserName(),
		Signature: fmt.Sprintf("%x-%x", sig.R, sig.S),
	})
	if err != nil {
		s.exit(err)
	}
}

var setupHostTemplate = template.Must(template.New("setuphost").Parse(`
Domain configuration and keys for the user
	upspin@{{.Domain}}
were generated and placed under the directory:
	{{.Dir}}

To prove that {{.UserName}} is the owner of {{.Domain}},
add the following record to {{.Domain}}'s DNS zone:

	NAME	TYPE	TTL	DATA
	@	TXT	15m	upspin:{{.Signature}}

Once the DNS change propagates the key server will use the TXT record to verify
that {{.UserName}} is authorized to register users under {{.Domain}}.

After that, the next step is to run 'upspin setupstorage'.
`))
<|endoftext|>


<commit_before>package margopher

import (
	"bytes"
	"io/ioutil"
	"log"
	"math/rand"
	"os"
	"regexp"
	"strings"
)

type margopher struct {
	states map[string][]string
}

// Margopher constructor
func NewMargopher() *margopher {
	return &margopher{states: make(map[string][]string)}
}

// Parse input text into states map
func (m *margopher) ParseText(text string) {
	words := strings.Split(text, " ")

	for i := 0; i < len(words)-1; i++ {
		if _, ok := m.states[words[i]]; ok {
			m.states[words[i]] = append(m.states[words[i]], words[i+1])
		} else {
			slice := []string{}
			slice = append(slice, words[i+1])
			m.states[words[i]] = slice
		}
	}
}

// Read text from file and send it to ParseText
func (m *margopher) ReadFile(filePath string) {
	// Open the file
	file, err := os.Open(filePath)
	if err != nil {
		log.Fatal(err)
	}

	// Read data from the file
	text, err := ioutil.ReadAll(file)
	if err != nil {
		log.Fatal(err)
	}

	// Call ParseText with the text
	m.ParseText(string(text))
}

// Extract keys from states map
func (m *margopher) extractKeys() []string {
	keys := make([]string, 0, len(m.states))
	for k := range m.states {
		keys = append(keys, k)
	}

	return keys
}

// Return a random element from a given string slice
func getRandomWord(slice []string) string {
	if !(cap(slice) == 0) {
		return slice[rand.Intn(len(slice))]
	} else {
		return ""
	}
}

// Confirm that a string word ends in '.'
func isTerminalWord(word string) bool {
	match, _ := regexp.MatchString("(\\.)$", word)
	return match
}

// Generate margopher senetence based on a given length
func (m *margopher) Generate(sentenceLength int) string {
	// Get all prefixes from states maps
	keys := m.extractKeys()

	var sentence bytes.Buffer

	// Initialize prefix with a random key
	prefix := getRandomWord(keys)
	sentence.WriteString(prefix + " ")

	for i := 1; i < sentenceLength; i++ {
		suffix := getRandomWord(m.states[prefix])
		sentence.WriteString(suffix + " ")

		// Break the loop if suffix ends in "." and senetenceLength is enough
		if isTerminalWord(suffix) && i > sentenceLength {
			break
		}

		prefix = suffix
	}

	return sentence.String()
}
<commit_msg>Rename ParseText to ReadText<commit_after>package margopher

import (
	"bytes"
	"io/ioutil"
	"log"
	"math/rand"
	"os"
	"regexp"
	"strings"
)

type margopher struct {
	states map[string][]string
}

// Margopher constructor
func NewMargopher() *margopher {
	return &margopher{states: make(map[string][]string)}
}

// Read input text into states map
func (m *margopher) ReadText(text string) {
	words := strings.Split(text, " ")

	for i := 0; i < len(words)-1; i++ {
		if _, ok := m.states[words[i]]; ok {
			m.states[words[i]] = append(m.states[words[i]], words[i+1])
		} else {
			slice := []string{}
			slice = append(slice, words[i+1])
			m.states[words[i]] = slice
		}
	}
}

// Read text from file and send it to ReadText
func (m *margopher) ReadFile(filePath string) {
	// Open the file
	file, err := os.Open(filePath)
	if err != nil {
		log.Fatal(err)
	}

	// Read data from the file
	text, err := ioutil.ReadAll(file)
	if err != nil {
		log.Fatal(err)
	}

	// Call ReadText with the text
	m.ReadText(string(text))
}

// Extract keys from states map
func (m *margopher) extractKeys() []string {
	keys := make([]string, 0, len(m.states))
	for k := range m.states {
		keys = append(keys, k)
	}

	return keys
}

// Return a random element from a given string slice
func getRandomWord(slice []string) string {
	if !(cap(slice) == 0) {
		return slice[rand.Intn(len(slice))]
	} else {
		return ""
	}
}

// Confirm that a string word ends in '.'
func isTerminalWord(word string) bool {
	match, _ := regexp.MatchString("(\\.)$", word)
	return match
}

// Generate margopher senetence based on a given length
func (m *margopher) Generate(sentenceLength int) string {
	// Get all prefixes from states maps
	keys := m.extractKeys()

	var sentence bytes.Buffer

	// Initialize prefix with a random key
	prefix := getRandomWord(keys)
	sentence.WriteString(prefix + " ")

	for i := 1; i < sentenceLength; i++ {
		suffix := getRandomWord(m.states[prefix])
		sentence.WriteString(suffix + " ")

		// Break the loop if suffix ends in "." and senetenceLength is enough
		if isTerminalWord(suffix) && i > sentenceLength {
			break
		}

		prefix = suffix
	}

	return sentence.String()
}
<|endoftext|>


<commit_before>// Copyright 2018 The Moov Authors
// Use of this source code is governed by an Apache License
// license that can be found in the LICENSE file.

package ach

import (
	"fmt"
	"strconv"
	"strings"
	"unicode/utf8"
)

// ADVEntryDetail contains the actual transaction data for an individual entry.
// Fields include those designating the entry as a deposit (credit) or
// withdrawal (debit), the transit routing number for the entry recipient’s financial
// institution, the account number (left justify,no zero fill), name, and dollar amount.
type ADVEntryDetail struct {
	// ID is a client defined string used as a reference to this record.
	ID string `json:"id"`
	// RecordType defines the type of record in the block. 6
	recordType string
	// TransactionCode representing Accounting Entries
	// Credit for ACH debits originated - 81
	// Debit for ACH credits originated - 82
	// Credit for ACH credits received 83
	// Debit for ACH debits received 84
	// Credit for ACH credits in rejected batches 85
	// Debit for ACH debits in rejected batches - 86
	// Summary credit for respondent ACH activity - 87
	// Summary debit for respondent ACH activity - 88
	TransactionCode int `json:"transactionCode"`
	// RDFIIdentification is the RDFI's routing number without the last digit.
	// Receiving Depository Financial Institution
	RDFIIdentification string `json:"RDFIIdentification"`
	// CheckDigit the last digit of the RDFI's routing number
	CheckDigit string `json:"checkDigit"`
	// DFIAccountNumber is the receiver's bank account number you are crediting/debiting.
	// It important to note that this is an alphanumeric field, so its space padded, no zero padded
	DFIAccountNumber string `json:"DFIAccountNumber"`
	// Amount Number of cents you are debiting/crediting this account
	Amount int `json:"amount"`
	// AdviceRoutingNumber
	AdviceRoutingNumber string `json:"adviceRoutingNumber"`
	// FileIdentification
	FileIdentification string `json:"fileIdentification,omitempty"`
	// ACHOperatorData
	ACHOperatorData string `json:"achOperatorData,omitempty"`
	// IndividualName The name of the receiver, usually the name on the bank account
	IndividualName string `json:"individualName"`
	// DiscretionaryData allows ODFIs to include codes, of significance only to them,
	// to enable specialized handling of the entry. There will be no
	// standardized interpretation for the value of this field. It can either
	// be a single two-character code, or two distinct one-character codes,
	// according to the needs of the ODFI and/or Originator involved. This
	// field must be returned intact for any returned entry.
	DiscretionaryData string `json:"discretionaryData,omitempty"`
	// AddendaRecordIndicator indicates the existence of an Addenda Record.
	// A value of "1" indicates that one ore more addenda records follow,
	// and "0" means no such record is present.
	AddendaRecordIndicator int `json:"addendaRecordIndicator,omitempty"`
	// ACHOperatorRoutingNumber
	ACHOperatorRoutingNumber string `json:"achOperatorRoutingNumber"`
	// JulianDateDay
	JulianDateDay int `json:"julianDateDay"`
	// SequenceNumber
	SequenceNumber int `json:"sequenceNumber,omitEmpty"`
	// Addenda99 for use with Returns
	Addenda99 *Addenda99 `json:"addenda99,omitempty"`
	// Category defines if the entry is a Forward, Return, or NOC
	Category string `json:"category,omitempty"`
	// validator is composed for data validation
	validator
	// converters is composed for ACH to golang Converters
	converters
}

// NewADVEntryDetail returns a new ADVEntryDetail with default values for non exported fields
func NewADVEntryDetail() *ADVEntryDetail {
	entry := &ADVEntryDetail{
		recordType: "6",
		Category:   CategoryForward,
	}
	return entry
}

// Parse takes the input record string and parses the ADVEntryDetail values
// Parse provides no guarantee about all fields being filled in. Callers should make a Validate() call to confirm
// successful parsing and data validity.

func (ed *ADVEntryDetail) Parse(record string) {
	if utf8.RuneCountInString(record) != 94 {
		return
	}

	// 1-1 Always "6"
	ed.recordType = "6"
	// 2-3 is checking credit 22 debit 27 savings credit 32 debit 37
	ed.TransactionCode = ed.parseNumField(record[1:3])
	// 4-11 the RDFI's routing number without the last digit.
	ed.RDFIIdentification = ed.parseStringField(record[3:11])
	// 12-12 The last digit of the RDFI's routing number
	ed.CheckDigit = ed.parseStringField(record[11:12])
	// 13-27 The receiver's bank account number you are crediting/debiting
	ed.DFIAccountNumber = record[12:27]
	// 28-39 Number of cents you are debiting/crediting this account
	ed.Amount = ed.parseNumField(record[27:39])
	// 40-48 Advice Routing Number
	ed.AdviceRoutingNumber = ed.parseStringField(record[39:48])
	// 49-53 File Identification
	ed.FileIdentification = ed.parseStringField(record[48:53])
	// 54-54 ACH Operator Data
	ed.ACHOperatorData = ed.parseStringField(record[53:54])
	// 55-76 Individual Name
	ed.IndividualName = record[54:76]
	// 77-78 allows ODFIs to include codes of significance only to them, normally blank
	ed.DiscretionaryData = record[76:78]
	// 79-79 1 if addenda exists 0 if it does not
	ed.AddendaRecordIndicator = ed.parseNumField(record[78:79])
	// 80-87
	ed.ACHOperatorRoutingNumber = ed.parseStringField(record[79:87])
	// 88-90
	ed.JulianDateDay = ed.parseNumField(record[87:90])
	// 91-94
	ed.SequenceNumber = ed.parseNumField(record[90:94])
}

// String writes the ADVEntryDetail struct to a 94 character string.
func (ed *ADVEntryDetail) String() string {
	var buf strings.Builder
	buf.Grow(94)
	buf.WriteString(ed.recordType)
	buf.WriteString(fmt.Sprintf("%v", ed.TransactionCode))
	buf.WriteString(ed.RDFIIdentificationField())
	buf.WriteString(ed.CheckDigit)
	buf.WriteString(ed.DFIAccountNumberField())
	buf.WriteString(ed.AmountField())
	buf.WriteString(ed.AdviceRoutingNumberField())
	buf.WriteString(ed.FileIdentificationField())
	buf.WriteString(ed.ACHOperatorDataField())
	buf.WriteString(ed.IndividualNameField())
	buf.WriteString(ed.DiscretionaryDataField())
	buf.WriteString(fmt.Sprintf("%v", ed.AddendaRecordIndicator))
	buf.WriteString(ed.ACHOperatorRoutingNumberField())
	buf.WriteString(ed.JulianDateDayField())
	buf.WriteString(ed.SequenceNumberField())
	return buf.String()
}

// Validate performs NACHA format rule checks on the record and returns an error if not Validated
// The first error encountered is returned and stops that parsing.
func (ed *ADVEntryDetail) Validate() error {
	if err := ed.fieldInclusion(); err != nil {
		return err
	}
	if ed.recordType != "6" {
		msg := fmt.Sprintf(msgRecordType, 6)
		return &FieldError{FieldName: "recordType", Value: ed.recordType, Msg: msg}
	}
	if err := ed.isTransactionCode(ed.TransactionCode); err != nil {
		return &FieldError{FieldName: "TransactionCode", Value: strconv.Itoa(ed.TransactionCode), Msg: err.Error()}
	}
	if err := ed.isAlphanumeric(ed.DFIAccountNumber); err != nil {
		return &FieldError{FieldName: "DFIAccountNumber", Value: ed.DFIAccountNumber, Msg: err.Error()}
	}
	if err := ed.isAlphanumeric(ed.AdviceRoutingNumber); err != nil {
		return &FieldError{FieldName: "AdviceRoutingNumber", Value: ed.AdviceRoutingNumber, Msg: err.Error()}
	}
	if err := ed.isAlphanumeric(ed.IndividualName); err != nil {
		return &FieldError{FieldName: "IndividualName", Value: ed.IndividualName, Msg: err.Error()}
	}
	if err := ed.isAlphanumeric(ed.DiscretionaryData); err != nil {
		return &FieldError{FieldName: "DiscretionaryData", Value: ed.DiscretionaryData, Msg: err.Error()}
	}
	if err := ed.isAlphanumeric(ed.ACHOperatorRoutingNumber); err != nil {
		return &FieldError{FieldName: "ACHOperatorRoutingNumber", Value: ed.ACHOperatorRoutingNumber, Msg: err.Error()}
	}
	calculated := ed.CalculateCheckDigit(ed.RDFIIdentificationField())

	edCheckDigit, _ := strconv.Atoi(ed.CheckDigit)

	if calculated != edCheckDigit {
		msg := fmt.Sprintf(msgValidCheckDigit, calculated)
		return &FieldError{FieldName: "RDFIIdentification", Value: ed.CheckDigit, Msg: msg}
	}
	return nil
}

// fieldInclusion validate mandatory fields are not default values. If fields are
// invalid the ACH transfer will be returned.
func (ed *ADVEntryDetail) fieldInclusion() error {
	if ed.recordType == "" {
		return &FieldError{
			FieldName: "recordType",
			Value:     ed.recordType,
			Msg:       msgFieldInclusion + ", did you use NewADVEntryDetail()?",
		}
	}
	if ed.TransactionCode == 0 {
		return &FieldError{
			FieldName: "TransactionCode",
			Value:     strconv.Itoa(ed.TransactionCode),
			Msg:       msgFieldInclusion + ", did you use NewADVEntryDetail()?",
		}
	}
	if ed.RDFIIdentification == "" {
		return &FieldError{
			FieldName: "RDFIIdentification",
			Value:     ed.RDFIIdentificationField(),
			Msg:       msgFieldInclusion + ", did you use NewADVEntryDetail()?",
		}
	}
	if ed.DFIAccountNumber == "" {
		return &FieldError{
			FieldName: "DFIAccountNumber",
			Value:     ed.DFIAccountNumber,
			Msg:       msgFieldInclusion + ", did you use NewADVEntryDetail()?",
		}
	}
	if ed.AdviceRoutingNumber == "" {
		return &FieldError{
			FieldName: "AdviceRoutingNumber",
			Value:     ed.AdviceRoutingNumber,
			Msg:       msgFieldInclusion + ", did you use NewADVEntryDetail()?",
		}
	}
	if ed.IndividualName == "" {
		return &FieldError{
			FieldName: "IndividualName",
			Value:     ed.IndividualName,
			Msg:       msgFieldRequired + ", did you use NewADVEntryDetail()?",
		}
	}
	if ed.ACHOperatorRoutingNumber == "" {
		return &FieldError{
			FieldName: "ACHOperatorRoutingNumber",
			Value:     ed.ACHOperatorRoutingNumber,
			Msg:       msgFieldInclusion + ", did you use NewADVEntryDetail()?",
		}
	}
	if ed.JulianDateDay == 0 {
		return &FieldError{
			FieldName: "JulianDateDay",
			Value:     strconv.Itoa(ed.JulianDateDay),
			Msg:       msgFieldInclusion + ", did you use NewADVEntryDetail()?",
		}
	}

	if ed.SequenceNumber == 0 {
		return &FieldError{
			FieldName: "SequenceNumber",
			Value:     strconv.Itoa(ed.SequenceNumber),
			Msg:       msgFieldInclusion + ", did you use NewADVEntryDetail()?",
		}
	}
	return nil
}

// SetRDFI takes the 9 digit RDFI account number and separates it for RDFIIdentification and CheckDigit
func (ed *ADVEntryDetail) SetRDFI(rdfi string) *ADVEntryDetail {
	s := ed.stringField(rdfi, 9)
	ed.RDFIIdentification = ed.parseStringField(s[:8])
	ed.CheckDigit = ed.parseStringField(s[8:9])
	return ed
}

// RDFIIdentificationField get the rdfiIdentification with zero padding
func (ed *ADVEntryDetail) RDFIIdentificationField() string {
	return ed.stringField(ed.RDFIIdentification, 8)
}

// DFIAccountNumberField gets the DFIAccountNumber with space padding
func (ed *ADVEntryDetail) DFIAccountNumberField() string {
	return ed.alphaField(ed.DFIAccountNumber, 15)
}

// AmountField returns a zero padded string of amount
func (ed *ADVEntryDetail) AmountField() string {
	return ed.numericField(ed.Amount, 12)
}

// AdviceRoutingNumberField gets the AdviceRoutingNumber with zero padding
func (ed *ADVEntryDetail) AdviceRoutingNumberField() string {
	return ed.stringField(ed.AdviceRoutingNumber, 9)
}

// FileIdentificationField returns a space padded string of FileIdentification
func (ed *ADVEntryDetail) FileIdentificationField() string {
	return ed.alphaField(ed.FileIdentification, 5)
}

// ACHOperatorDataField returns a space padded string of ACHOperatorData
func (ed *ADVEntryDetail) ACHOperatorDataField() string {
	return ed.alphaField(ed.ACHOperatorData, 1)
}

// IndividualNameField returns a space padded string of IndividualName
func (ed *ADVEntryDetail) IndividualNameField() string {
	return ed.alphaField(ed.IndividualName, 22)
}

// DiscretionaryDataField returns a space padded string of DiscretionaryData
func (ed *ADVEntryDetail) DiscretionaryDataField() string {
	return ed.alphaField(ed.DiscretionaryData, 2)
}

// ACHOperatorRoutingNumberField returns a space padded string of ACHOperatorRoutingNumber
func (ed *ADVEntryDetail) ACHOperatorRoutingNumberField() string {
	return ed.alphaField(ed.ACHOperatorRoutingNumber, 8)
}

// JulianDateDayField returns a zero padded string of JulianDateDay
func (ed *ADVEntryDetail) JulianDateDayField() string {
	return ed.numericField(ed.JulianDateDay, 3)
}

// SequenceNumberField returns a zero padded string of SequenceNumber
func (ed *ADVEntryDetail) SequenceNumberField() string {
	return ed.numericField(ed.SequenceNumber, 4)
}
<commit_msg>Update advEntryDetail.go<commit_after>// Copyright 2018 The Moov Authors
// Use of this source code is governed by an Apache License
// license that can be found in the LICENSE file.

package ach

import (
	"fmt"
	"strconv"
	"strings"
	"unicode/utf8"
)

// ADVEntryDetail contains the actual transaction data for an individual entry.
// Fields include those designating the entry as a deposit (credit) or
// withdrawal (debit), the transit routing number for the entry recipient’s financial
// institution, the account number (left justify,no zero fill), name, and dollar amount.
type ADVEntryDetail struct {
	// ID is a client defined string used as a reference to this record.
	ID string `json:"id"`
	// RecordType defines the type of record in the block. 6
	recordType string
	// TransactionCode representing Accounting Entries
	// Credit for ACH debits originated - 81
	// Debit for ACH credits originated - 82
	// Credit for ACH credits received 83
	// Debit for ACH debits received 84
	// Credit for ACH credits in rejected batches 85
	// Debit for ACH debits in rejected batches - 86
	// Summary credit for respondent ACH activity - 87
	// Summary debit for respondent ACH activity - 88
	TransactionCode int `json:"transactionCode"`
	// RDFIIdentification is the RDFI's routing number without the last digit.
	// Receiving Depository Financial Institution
	RDFIIdentification string `json:"RDFIIdentification"`
	// CheckDigit the last digit of the RDFI's routing number
	CheckDigit string `json:"checkDigit"`
	// DFIAccountNumber is the receiver's bank account number you are crediting/debiting.
	// It important to note that this is an alphanumeric field, so its space padded, no zero padded
	DFIAccountNumber string `json:"DFIAccountNumber"`
	// Amount Number of cents you are debiting/crediting this account
	Amount int `json:"amount"`
	// AdviceRoutingNumber
	AdviceRoutingNumber string `json:"adviceRoutingNumber"`
	// FileIdentification
	FileIdentification string `json:"fileIdentification,omitempty"`
	// ACHOperatorData
	ACHOperatorData string `json:"achOperatorData,omitempty"`
	// IndividualName The name of the receiver, usually the name on the bank account
	IndividualName string `json:"individualName"`
	// DiscretionaryData allows ODFIs to include codes, of significance only to them,
	// to enable specialized handling of the entry. There will be no
	// standardized interpretation for the value of this field. It can either
	// be a single two-character code, or two distinct one-character codes,
	// according to the needs of the ODFI and/or Originator involved. This
	// field must be returned intact for any returned entry.
	DiscretionaryData string `json:"discretionaryData,omitempty"`
	// AddendaRecordIndicator indicates the existence of an Addenda Record.
	// A value of "1" indicates that one ore more addenda records follow,
	// and "0" means no such record is present.
	AddendaRecordIndicator int `json:"addendaRecordIndicator,omitempty"`
	// ACHOperatorRoutingNumber
	ACHOperatorRoutingNumber string `json:"achOperatorRoutingNumber"`
	// JulianDateDay
	JulianDateDay int `json:"julianDateDay"`
	// SequenceNumber
	SequenceNumber int `json:"sequenceNumber,omitEmpty"`
	// Addenda99 for use with Returns
	Addenda99 *Addenda99 `json:"addenda99,omitempty"`
	// Category defines if the entry is a Forward, Return, or NOC
	Category string `json:"category,omitempty"`
	// validator is composed for data validation
	validator
	// converters is composed for ACH to golang Converters
	converters
}

// NewADVEntryDetail returns a new ADVEntryDetail with default values for non exported fields
func NewADVEntryDetail() *ADVEntryDetail {
	entry := &ADVEntryDetail{
		recordType: "6",
		Category:   CategoryForward,
	}
	return entry
}

// Parse takes the input record string and parses the ADVEntryDetail values
// Parse provides no guarantee about all fields being filled in. Callers should make a Validate() call to confirm
// successful parsing and data validity.

func (ed *ADVEntryDetail) Parse(record string) {
	if utf8.RuneCountInString(record) != 94 {
		return
	}

	// 1-1 Always "6"
	ed.recordType = "6"
	// 2-3 is checking credit 22 debit 27 savings credit 32 debit 37
	ed.TransactionCode = ed.parseNumField(record[1:3])
	// 4-11 the RDFI's routing number without the last digit.
	ed.RDFIIdentification = ed.parseStringField(record[3:11])
	// 12-12 The last digit of the RDFI's routing number
	ed.CheckDigit = ed.parseStringField(record[11:12])
	// 13-27 The receiver's bank account number you are crediting/debiting
	ed.DFIAccountNumber = record[12:27]
	// 28-39 Number of cents you are debiting/crediting this account
	ed.Amount = ed.parseNumField(record[27:39])
	// 40-48 Advice Routing Number
	ed.AdviceRoutingNumber = ed.parseStringField(record[39:48])
	// 49-53 File Identification
	ed.FileIdentification = ed.parseStringField(record[48:53])
	// 54-54 ACH Operator Data
	ed.ACHOperatorData = ed.parseStringField(record[53:54])
	// 55-76 Individual Name
	ed.IndividualName = record[54:76]
	// 77-78 allows ODFIs to include codes of significance only to them, normally blank
	ed.DiscretionaryData = record[76:78]
	// 79-79 1 if addenda exists 0 if it does not
	ed.AddendaRecordIndicator = ed.parseNumField(record[78:79])
	// 80-87
	ed.ACHOperatorRoutingNumber = ed.parseStringField(record[79:87])
	// 88-90
	ed.JulianDateDay = ed.parseNumField(record[87:90])
	// 91-94
	ed.SequenceNumber = ed.parseNumField(record[90:94])
}

// String writes the ADVEntryDetail struct to a 94 character string.
func (ed *ADVEntryDetail) String() string {
	var buf strings.Builder
	buf.Grow(94)
	buf.WriteString(ed.recordType)
	buf.WriteString(fmt.Sprintf("%v", ed.TransactionCode))
	buf.WriteString(ed.RDFIIdentificationField())
	buf.WriteString(ed.CheckDigit)
	buf.WriteString(ed.DFIAccountNumberField())
	buf.WriteString(ed.AmountField())
	buf.WriteString(ed.AdviceRoutingNumberField())
	buf.WriteString(ed.FileIdentificationField())
	buf.WriteString(ed.ACHOperatorDataField())
	buf.WriteString(ed.IndividualNameField())
	buf.WriteString(ed.DiscretionaryDataField())
	buf.WriteString(fmt.Sprintf("%v", ed.AddendaRecordIndicator))
	buf.WriteString(ed.ACHOperatorRoutingNumberField())
	buf.WriteString(ed.JulianDateDayField())
	buf.WriteString(ed.SequenceNumberField())
	return buf.String()
}

// Validate performs NACHA format rule checks on the record and returns an error if not Validated
// The first error encountered is returned and stops that parsing.
func (ed *ADVEntryDetail) Validate() error {
	if err := ed.fieldInclusion(); err != nil {
		return err
	}
	if ed.recordType != "6" {
		msg := fmt.Sprintf(msgRecordType, 6)
		return &FieldError{FieldName: "recordType", Value: ed.recordType, Msg: msg}
	}
	if err := ed.isTransactionCode(ed.TransactionCode); err != nil {
		return &FieldError{FieldName: "TransactionCode", Value: strconv.Itoa(ed.TransactionCode), Msg: err.Error()}
	}
	if err := ed.isAlphanumeric(ed.DFIAccountNumber); err != nil {
		return &FieldError{FieldName: "DFIAccountNumber", Value: ed.DFIAccountNumber, Msg: err.Error()}
	}
	if err := ed.isAlphanumeric(ed.AdviceRoutingNumber); err != nil {
		return &FieldError{FieldName: "AdviceRoutingNumber", Value: ed.AdviceRoutingNumber, Msg: err.Error()}
	}
	if err := ed.isAlphanumeric(ed.IndividualName); err != nil {
		return &FieldError{FieldName: "IndividualName", Value: ed.IndividualName, Msg: err.Error()}
	}
	if err := ed.isAlphanumeric(ed.DiscretionaryData); err != nil {
		return &FieldError{FieldName: "DiscretionaryData", Value: ed.DiscretionaryData, Msg: err.Error()}
	}
	if err := ed.isAlphanumeric(ed.ACHOperatorRoutingNumber); err != nil {
		return &FieldError{FieldName: "ACHOperatorRoutingNumber", Value: ed.ACHOperatorRoutingNumber, Msg: err.Error()}
	}
	calculated := ed.CalculateCheckDigit(ed.RDFIIdentificationField())

	edCheckDigit, _ := strconv.Atoi(ed.CheckDigit)

	if calculated != edCheckDigit {
		msg := fmt.Sprintf(msgValidCheckDigit, calculated)
		return &FieldError{FieldName: "RDFIIdentification", Value: ed.CheckDigit, Msg: msg}
	}
	return nil
}

// fieldInclusion validate mandatory fields are not default values. If fields are
// invalid the ACH transfer will be returned.
func (ed *ADVEntryDetail) fieldInclusion() error {
	if ed.recordType == "" {
		return &FieldError{
			FieldName: "recordType",
			Value:     ed.recordType,
			Msg:       msgFieldInclusion + ", did you use NewADVEntryDetail()?",
		}
	}
	if ed.TransactionCode == 0 {
		return &FieldError{
			FieldName: "TransactionCode",
			Value:     strconv.Itoa(ed.TransactionCode),
			Msg:       msgFieldInclusion + ", did you use NewADVEntryDetail()?",
		}
	}
	if ed.RDFIIdentification == "" {
		return &FieldError{
			FieldName: "RDFIIdentification",
			Value:     ed.RDFIIdentificationField(),
			Msg:       msgFieldInclusion + ", did you use NewADVEntryDetail()?",
		}
	}
	if ed.DFIAccountNumber == "" {
		return &FieldError{
			FieldName: "DFIAccountNumber",
			Value:     ed.DFIAccountNumber,
			Msg:       msgFieldInclusion + ", did you use NewADVEntryDetail()?",
		}
	}
	if ed.AdviceRoutingNumber == "" {
		return &FieldError{
			FieldName: "AdviceRoutingNumber",
			Value:     ed.AdviceRoutingNumber,
			Msg:       msgFieldInclusion + ", did you use NewADVEntryDetail()?",
		}
	}
	if ed.IndividualName == "" {
		return &FieldError{
			FieldName: "IndividualName",
			Value:     ed.IndividualName,
			Msg:       msgFieldRequired + ", did you use NewADVEntryDetail()?",
		}
	}
	if ed.ACHOperatorRoutingNumber == "" {
		return &FieldError{
			FieldName: "ACHOperatorRoutingNumber",
			Value:     ed.ACHOperatorRoutingNumber,
			Msg:       msgFieldInclusion + ", did you use NewADVEntryDetail()?",
		}
	}
	if ed.JulianDateDay <= 0 {
		return &FieldError{
			FieldName: "JulianDateDay",
			Value:     strconv.Itoa(ed.JulianDateDay),
			Msg:       msgFieldInclusion + ", did you use NewADVEntryDetail()?",
		}
	}

	if ed.SequenceNumber == 0 {
		return &FieldError{
			FieldName: "SequenceNumber",
			Value:     strconv.Itoa(ed.SequenceNumber),
			Msg:       msgFieldInclusion + ", did you use NewADVEntryDetail()?",
		}
	}
	return nil
}

// SetRDFI takes the 9 digit RDFI account number and separates it for RDFIIdentification and CheckDigit
func (ed *ADVEntryDetail) SetRDFI(rdfi string) *ADVEntryDetail {
	s := ed.stringField(rdfi, 9)
	ed.RDFIIdentification = ed.parseStringField(s[:8])
	ed.CheckDigit = ed.parseStringField(s[8:9])
	return ed
}

// RDFIIdentificationField get the rdfiIdentification with zero padding
func (ed *ADVEntryDetail) RDFIIdentificationField() string {
	return ed.stringField(ed.RDFIIdentification, 8)
}

// DFIAccountNumberField gets the DFIAccountNumber with space padding
func (ed *ADVEntryDetail) DFIAccountNumberField() string {
	return ed.alphaField(ed.DFIAccountNumber, 15)
}

// AmountField returns a zero padded string of amount
func (ed *ADVEntryDetail) AmountField() string {
	return ed.numericField(ed.Amount, 12)
}

// AdviceRoutingNumberField gets the AdviceRoutingNumber with zero padding
func (ed *ADVEntryDetail) AdviceRoutingNumberField() string {
	return ed.stringField(ed.AdviceRoutingNumber, 9)
}

// FileIdentificationField returns a space padded string of FileIdentification
func (ed *ADVEntryDetail) FileIdentificationField() string {
	return ed.alphaField(ed.FileIdentification, 5)
}

// ACHOperatorDataField returns a space padded string of ACHOperatorData
func (ed *ADVEntryDetail) ACHOperatorDataField() string {
	return ed.alphaField(ed.ACHOperatorData, 1)
}

// IndividualNameField returns a space padded string of IndividualName
func (ed *ADVEntryDetail) IndividualNameField() string {
	return ed.alphaField(ed.IndividualName, 22)
}

// DiscretionaryDataField returns a space padded string of DiscretionaryData
func (ed *ADVEntryDetail) DiscretionaryDataField() string {
	return ed.alphaField(ed.DiscretionaryData, 2)
}

// ACHOperatorRoutingNumberField returns a space padded string of ACHOperatorRoutingNumber
func (ed *ADVEntryDetail) ACHOperatorRoutingNumberField() string {
	return ed.alphaField(ed.ACHOperatorRoutingNumber, 8)
}

// JulianDateDayField returns a zero padded string of JulianDateDay
func (ed *ADVEntryDetail) JulianDateDayField() string {
	return ed.numericField(ed.JulianDateDay, 3)
}

// SequenceNumberField returns a zero padded string of SequenceNumber
func (ed *ADVEntryDetail) SequenceNumberField() string {
	return ed.numericField(ed.SequenceNumber, 4)
}
<|endoftext|>


<commit_before>package models

import (
	"errors"
	"strings"
	"time"

	"github.com/gobuild/gobuild2/pkg/base"
	"github.com/gobuild/gobuild2/pkg/gowalker"
	"github.com/gobuild/log"
)

var (
	ErrRepositoryNotExists = errors.New("repo not found")
)

type Repository struct {
	Id      int64
	Uri     string `xorm:"unique(r)"`
	Brief   string
	IsCgo   bool
	IsCmd   bool
	Tags    []string
	Created time.Time `xorm:"created"`
}

type RepoStatistic struct {
	Rid           int64 `xorm:"pk"`
	Pv            int64
	DownloadCount int64
	Updated       time.Time `xorm:"updated"`
}

type LastRepoUpdate struct {
	Rid        int64  `xorm:"unique(u)"`
	TagBranch  string `xorm:"unique(u)"`
	Os         string `xorm:"unique(u)"`
	Arch       string `xorm:"unique(u)"`
	PushURI    string
	ZipBallUrl string
	Updated    time.Time `xorm:"updated"`
}

func AddRepository(repoName string) (r *Repository, err error) {
	cvsinfo, err := base.ParseCvsURI(repoName) // base.SanitizedRepoPath(rf.Name)
	if err != nil {
		log.Errorf("parse cvs url error: %v", err)
		return
	}

	repoUri := cvsinfo.FullPath
	r = new(Repository)
	r.Uri = repoUri

	pkginfo, err := gowalker.GetPkgInfo(repoUri)
	if err != nil {
		log.Errorf("gowalker not passed check: %v", err)
		return
	}
	r.IsCgo = pkginfo.IsCgo
	r.IsCmd = pkginfo.IsCmd
	r.Tags = strings.Split(pkginfo.Tags, "|||")
	// description
	r.Brief = pkginfo.Description
	base.ParseCvsURI(repoUri)
	if strings.HasPrefix(repoUri, "github.com") {
		// comunicate with github
		fields := strings.Split(repoUri, "/")
		owner, repoName := fields[1], fields[2]
		repo, _, err := GHClient.Repositories.Get(owner, repoName)
		if err != nil {
			log.Errorf("get information from github error: %v", err)
		} else {
			r.Brief = *repo.Description
		}
	}
	if _, err = CreateRepository(r); err != nil {
		log.Errorf("create repo error: %v", err)
		return
	}
	return r, nil
}

func UpdateRepository(v *Repository, condi *Repository) (int64, error) {
	return orm.UseBool().Update(v, condi)
}

func CreateRepository(r *Repository) (*Repository, error) {
	// r := &Repository{Uri: repoUri}
	if has, err := orm.Get(r); err == nil && has {
		return r, nil
	}
	_, err := orm.Insert(r)
	return r, err
}

func GetAllRepos(count, start int) ([]Repository, error) {
	var rs []Repository
	err := orm.Limit(count, start).Desc("created").Find(&rs)
	return rs, err
}

func GetRepositoryById(id int64) (*Repository, error) {
	r := new(Repository)
	if has, err := orm.Id(id).Get(r); err == nil && has {
		return r, nil
	}
	return nil, ErrRepositoryNotExists
}

func GetRepositoryByName(name string) (*Repository, error) {
	r := &Repository{Uri: name}
	if has, err := orm.Get(r); err == nil && has {
		return r, nil
	}
	return nil, ErrRepositoryNotExists
}

func GetAllLastRepoByOsArch(os, arch string) (us []LastRepoUpdate, err error) {
	err = orm.Asc("rid").Find(&us, &LastRepoUpdate{Os: os, Arch: arch})
	return us, err
}

func GetAllLastRepoUpdate(rid int64) (us []LastRepoUpdate, err error) {
	err = orm.Find(&us, &LastRepoUpdate{Rid: rid})
	return
}
<commit_msg>add download count in repo<commit_after>package models

import (
	"errors"
	"strings"
	"time"

	"github.com/gobuild/gobuild2/pkg/base"
	"github.com/gobuild/gobuild2/pkg/gowalker"
	"github.com/gobuild/log"
)

var (
	ErrRepositoryNotExists = errors.New("repo not found")
)

type Repository struct {
	Id            int64
	Uri           string `xorm:"unique(r)"`
	Brief         string
	IsCgo         bool
	IsCmd         bool
	Tags          []string
	Created       time.Time `xorm:"created"`
	DownloadCount int64
}

type RepoStatistic struct {
	Rid           int64 `xorm:"pk"`
	Pv            int64
	DownloadCount int64
	Updated       time.Time `xorm:"updated"`
}

type LastRepoUpdate struct {
	Rid        int64  `xorm:"unique(u)"`
	TagBranch  string `xorm:"unique(u)"`
	Os         string `xorm:"unique(u)"`
	Arch       string `xorm:"unique(u)"`
	PushURI    string
	ZipBallUrl string
	Updated    time.Time `xorm:"updated"`
}

func AddRepository(repoName string) (r *Repository, err error) {
	cvsinfo, err := base.ParseCvsURI(repoName) // base.SanitizedRepoPath(rf.Name)
	if err != nil {
		log.Errorf("parse cvs url error: %v", err)
		return
	}

	repoUri := cvsinfo.FullPath
	r = new(Repository)
	r.Uri = repoUri

	pkginfo, err := gowalker.GetPkgInfo(repoUri)
	if err != nil {
		log.Errorf("gowalker not passed check: %v", err)
		return
	}
	r.IsCgo = pkginfo.IsCgo
	r.IsCmd = pkginfo.IsCmd
	r.Tags = strings.Split(pkginfo.Tags, "|||")
	// description
	r.Brief = pkginfo.Description
	base.ParseCvsURI(repoUri)
	if strings.HasPrefix(repoUri, "github.com") {
		// comunicate with github
		fields := strings.Split(repoUri, "/")
		owner, repoName := fields[1], fields[2]
		repo, _, err := GHClient.Repositories.Get(owner, repoName)
		if err != nil {
			log.Errorf("get information from github error: %v", err)
		} else {
			r.Brief = *repo.Description
		}
	}
	if _, err = CreateRepository(r); err != nil {
		log.Errorf("create repo error: %v", err)
		return
	}
	return r, nil
}

func UpdateRepository(v *Repository, condi *Repository) (int64, error) {
	return orm.UseBool().Update(v, condi)
}

func CreateRepository(r *Repository) (*Repository, error) {
	// r := &Repository{Uri: repoUri}
	if has, err := orm.Get(r); err == nil && has {
		return r, nil
	}
	_, err := orm.Insert(r)
	return r, err
}

func GetAllRepos(count, start int) ([]Repository, error) {
	var rs []Repository
	err := orm.Limit(count, start).Desc("created").Find(&rs)
	return rs, err
}

func GetRepositoryById(id int64) (*Repository, error) {
	r := new(Repository)
	if has, err := orm.Id(id).Get(r); err == nil && has {
		return r, nil
	}
	return nil, ErrRepositoryNotExists
}

func GetRepositoryByName(name string) (*Repository, error) {
	r := &Repository{Uri: name}
	if has, err := orm.Get(r); err == nil && has {
		return r, nil
	}
	return nil, ErrRepositoryNotExists
}

func GetAllLastRepoByOsArch(os, arch string) (us []LastRepoUpdate, err error) {
	err = orm.Asc("rid").Find(&us, &LastRepoUpdate{Os: os, Arch: arch})
	return us, err
}

func GetAllLastRepoUpdate(rid int64) (us []LastRepoUpdate, err error) {
	err = orm.Find(&us, &LastRepoUpdate{Rid: rid})
	return
}
<|endoftext|>


<commit_before>package note

import (
	"net/http"
	"net/url"
	"strconv"
	"strings"

	"github.com/cozy/cozy-stack/client/request"
	"github.com/cozy/cozy-stack/model/instance"
	"github.com/cozy/cozy-stack/model/permission"
	"github.com/cozy/cozy-stack/model/sharing"
	"github.com/cozy/cozy-stack/model/vfs"
	build "github.com/cozy/cozy-stack/pkg/config"
	"github.com/cozy/cozy-stack/pkg/config/config"
	"github.com/cozy/cozy-stack/pkg/consts"
	"github.com/cozy/cozy-stack/pkg/couchdb"
	"github.com/cozy/cozy-stack/pkg/jsonapi"
)

type apiNoteURL struct {
	DocID      string `json:"_id,omitempty"`
	NoteID     string `json:"note_id"`
	Protocol   string `json:"protocol"`
	Subdomain  string `json:"subdomain"`
	Instance   string `json:"instance"`
	Sharecode  string `json:"sharecode,omitempty"`
	PublicName string `json:"public_name,omitempty"`
}

func (n *apiNoteURL) ID() string                             { return n.DocID }
func (n *apiNoteURL) Rev() string                            { return "" }
func (n *apiNoteURL) DocType() string                        { return consts.NotesURL }
func (n *apiNoteURL) Clone() couchdb.Doc                     { cloned := *n; return &cloned }
func (n *apiNoteURL) SetID(id string)                        { n.DocID = id }
func (n *apiNoteURL) SetRev(rev string)                      {}
func (n *apiNoteURL) Relationships() jsonapi.RelationshipMap { return nil }
func (n *apiNoteURL) Included() []jsonapi.Object             { return nil }
func (n *apiNoteURL) Links() *jsonapi.LinksList              { return nil }
func (n *apiNoteURL) Fetch(field string) []string            { return nil }

// Opener can be used to find the parameters for creating the URL where the
// note can be opened.
type Opener struct {
	inst      *instance.Instance
	file      *vfs.FileDoc
	sharing   *sharing.Sharing // can be nil
	code      string
	clientID  string
	memberKey string
}

// Open will return an Opener for the given file.
func Open(inst *instance.Instance, fileID string) (*Opener, error) {
	file, err := inst.VFS().FileByID(fileID)
	if err != nil {
		return nil, err
	}

	// Check that the file is a note
	if _, err := fromMetadata(file); err != nil {
		return nil, err
	}

	// Looks if the note is shared
	sharing, err := getSharing(inst, fileID)
	if err != nil {
		return nil, err
	}

	return &Opener{inst: inst, file: file, sharing: sharing}, nil
}

func getSharing(inst *instance.Instance, fileID string) (*sharing.Sharing, error) {
	sid := consts.Files + "/" + fileID
	var ref sharing.SharedRef
	if err := couchdb.GetDoc(inst, consts.Shared, sid, &ref); err != nil {
		if couchdb.IsNotFoundError(err) {
			return nil, nil
		}
		return nil, err
	}

	for sharingID, info := range ref.Infos {
		if info.Removed {
			continue
		}
		var sharing sharing.Sharing
		if err := couchdb.GetDoc(inst, consts.Sharings, sharingID, &sharing); err != nil {
			return nil, err
		}
		if sharing.Active {
			return &sharing, nil
		}
	}
	return nil, nil
}

// AddShareByLinkCode can be used to give a sharecode that can be used to open
// the note, when the note is in a directory shared by link.
func (o *Opener) AddShareByLinkCode(code string) {
	o.code = code
}

// CheckPermission takes the permission doc, and checks that the user has the
// right to open the note.
func (o *Opener) CheckPermission(pdoc *permission.Permission, sharingID string) error {
	// If a note is opened from a preview of a sharing, and nobody has accepted
	// the sharing until now, the io.cozy.shared document for the note has not
	// been created, and we need to fill the sharing by another way.
	if o.sharing == nil && pdoc.Type == permission.TypeSharePreview {
		parts := strings.SplitN(pdoc.SourceID, "/", 2)
		if len(parts) != 2 {
			return sharing.ErrInvalidSharing
		}
		sharingID := parts[1]
		var sharing sharing.Sharing
		if err := couchdb.GetDoc(o.inst, consts.Sharings, sharingID, &sharing); err != nil {
			return err
		}
		o.sharing = &sharing
		preview, err := permission.GetForSharePreview(o.inst, sharingID)
		if err != nil {
			return err
		}
		for k, v := range preview.Codes {
			if v == o.code {
				o.memberKey = k
			}
		}
	}

	// If a note is opened via a token for cozy-to-cozy sharing, then the note
	// must be in this sharing, or the stack should refuse to open the note.
	if sharingID != "" && o.sharing != nil && o.sharing.ID() == sharingID {
		o.clientID = pdoc.SourceID
		return nil
	}

	fs := o.inst.VFS()
	return vfs.Allows(fs, pdoc.Permissions, permission.GET, o.file)
}

// GetResult looks if the note can be opened locally or not, which code can be
// used in case of a shared note, and other parameters.. and returns the information.
func (o *Opener) GetResult(memberIndex int, readOnly bool) (jsonapi.Object, error) {
	var result *apiNoteURL
	var err error
	if o.shouldOpenLocally() {
		result, err = o.openLocalNote(memberIndex, readOnly)
	} else {
		result, err = o.openSharedNote()
	}
	if err != nil {
		return nil, err
	}

	// Enforce DocID and PublicName with local values
	result.DocID = o.file.ID()
	if name, err := o.inst.PublicName(); err == nil {
		result.PublicName = name
	}
	return result, nil
}

func (o *Opener) shouldOpenLocally() bool {
	if o.sharing == nil {
		return true
	}
	u, err := url.Parse(o.file.CozyMetadata.CreatedOn)
	if err != nil {
		return true
	}
	return o.inst.HasDomain(u.Host)
}

func (o *Opener) openLocalNote(memberIndex int, readOnly bool) (*apiNoteURL, error) {
	code, err := o.getSharecode(memberIndex, readOnly)
	if err != nil {
		return nil, err
	}
	doc := &apiNoteURL{
		NoteID:    o.file.ID(),
		Instance:  o.inst.ContextualDomain(),
		Sharecode: code,
	}
	switch config.GetConfig().Subdomains {
	case config.FlatSubdomains:
		doc.Subdomain = "flat"
	case config.NestedSubdomains:
		doc.Subdomain = "nested"
	}
	doc.Protocol = "https"
	if build.IsDevRelease() {
		doc.Protocol = "http"
	}
	return doc, nil
}

func (o *Opener) openSharedNote() (*apiNoteURL, error) {
	s := o.sharing
	var creds *sharing.Credentials
	var creator *sharing.Member
	var memberIndex int
	readOnly := s.ReadOnlyRules()

	if s.Owner {
		domain := o.file.CozyMetadata.CreatedOn
		for i, m := range s.Members {
			if i == 0 {
				continue // Skip the owner
			}
			if m.Instance == domain || m.Instance+"/" == domain {
				creds = &s.Credentials[i-1]
				creator = &s.Members[i]
			}
		}
		if o.clientID != "" && !readOnly {
			for i, c := range s.Credentials {
				if c.InboundClientID == o.clientID {
					memberIndex = i + 1
					readOnly = s.Members[i+1].ReadOnly
				}
			}
		}
	} else {
		creds = &s.Credentials[0]
		creator = &s.Members[0]
	}

	if creator == nil || creator.Status != sharing.MemberStatusReady {
		// If the creator of the note is no longer in the sharing, the owner of
		// the sharing takes the lead, and if the sharing is revoked, any
		// member can edit the note on their instance.
		if o.clientID == "" {
			o.sharing = nil
		}
		return o.openLocalNote(memberIndex, readOnly)
	}

	xoredID := sharing.XorID(o.file.ID(), creds.XorKey)
	u, err := url.Parse(creator.Instance)
	if err != nil {
		return nil, err
	}
	opts := &request.Options{
		Method: http.MethodGet,
		Scheme: u.Scheme,
		Domain: u.Host,
		Path:   "/notes/" + xoredID + "/open",
		Queries: url.Values{
			"SharingID":   {s.ID()},
			"MemberIndex": {strconv.FormatInt(int64(memberIndex), 10)},
			"ReadOnly":    {strconv.FormatBool(readOnly)},
		},
		Headers: request.Headers{
			"Accept":        "application/vnd.api+json",
			"Authorization": "Bearer " + creds.AccessToken.AccessToken,
		},
	}
	res, err := request.Req(opts)
	if res != nil && res.StatusCode/100 == 4 {
		res, err = sharing.RefreshToken(o.inst, s, creator, creds, opts, nil)
	}
	if err != nil {
		return nil, sharing.ErrInternalServerError
	}
	defer res.Body.Close()
	var doc apiNoteURL
	if _, err := jsonapi.Bind(res.Body, &doc); err != nil {
		return nil, err
	}
	return &doc, nil
}

func (o *Opener) getSharecode(memberIndex int, readOnly bool) (string, error) {
	s := o.sharing
	if s == nil || (o.clientID == "" && o.memberKey == "") {
		return o.code, nil
	}

	var member *sharing.Member
	var err error
	if o.memberKey != "" {
		// Preview of a cozy-to-cozy sharing
		for i, m := range s.Members {
			if m.Instance == o.memberKey || m.Email == o.memberKey {
				member = &s.Members[i]
			}
		}
		if member == nil {
			return "", sharing.ErrMemberNotFound
		}
		if member.ReadOnly {
			readOnly = true
		} else {
			readOnly = s.ReadOnlyRules()
		}
	} else if s.Owner {
		member, err = s.FindMemberByInboundClientID(o.clientID)
		if err != nil {
			return "", err
		}
		if member.ReadOnly {
			readOnly = true
		} else {
			readOnly = s.ReadOnlyRules()
		}
	} else {
		// Trust the owner
		if memberIndex < 0 && memberIndex >= len(s.Members) {
			return "", sharing.ErrMemberNotFound
		}
		member = &s.Members[memberIndex]
	}

	if readOnly {
		return o.getPreviewCode(member)
	}
	return o.getInteractCode(member)
}

// getPreviewCode returns a sharecode that can be used for reading the note. It
// uses a share-preview token.
func (o *Opener) getPreviewCode(member *sharing.Member) (string, error) {
	var codes map[string]string
	preview, err := permission.GetForSharePreview(o.inst, o.sharing.ID())
	if err != nil {
		if couchdb.IsNotFoundError(err) {
			codes, err = o.sharing.CreatePreviewPermissions(o.inst)
		}
		if err != nil {
			return "", err
		}
	} else {
		codes = preview.Codes
	}

	for key, code := range codes {
		if key == member.Instance || key == member.Email {
			return code, nil
		}
	}

	return "", ErrInvalidFile
}

// getInteractCode returns a sharecode that can be use for reading and writing
// the note. It uses a share-interact token.
func (o *Opener) getInteractCode(member *sharing.Member) (string, error) {
	interact, err := permission.GetForShareInteract(o.inst, o.sharing.ID())
	if err != nil {
		if couchdb.IsNotFoundError(err) {
			return o.sharing.CreateInteractPermissions(o.inst, member)
		}
		return "", err
	}

	// If we already have a code for this member, let's use it
	for key, code := range interact.Codes {
		if key == member.Instance || key == member.Email {
			return code, nil
		}
	}

	// Else, create a code and add it to the permission doc
	key := member.Email
	if key == "" {
		key = member.Instance
	}
	code, err := o.inst.CreateShareCode(key)
	if err != nil {
		return "", err
	}
	interact.Codes[key] = code
	if err := couchdb.UpdateDoc(o.inst, interact); err != nil {
		return "", err
	}
	return code, nil
}
<commit_msg>Fix the previous fix<commit_after>package note

import (
	"net/http"
	"net/url"
	"strconv"
	"strings"

	"github.com/cozy/cozy-stack/client/request"
	"github.com/cozy/cozy-stack/model/instance"
	"github.com/cozy/cozy-stack/model/permission"
	"github.com/cozy/cozy-stack/model/sharing"
	"github.com/cozy/cozy-stack/model/vfs"
	build "github.com/cozy/cozy-stack/pkg/config"
	"github.com/cozy/cozy-stack/pkg/config/config"
	"github.com/cozy/cozy-stack/pkg/consts"
	"github.com/cozy/cozy-stack/pkg/couchdb"
	"github.com/cozy/cozy-stack/pkg/jsonapi"
	"github.com/cozy/cozy-stack/pkg/logger"
)

type apiNoteURL struct {
	DocID      string `json:"_id,omitempty"`
	NoteID     string `json:"note_id"`
	Protocol   string `json:"protocol"`
	Subdomain  string `json:"subdomain"`
	Instance   string `json:"instance"`
	Sharecode  string `json:"sharecode,omitempty"`
	PublicName string `json:"public_name,omitempty"`
}

func (n *apiNoteURL) ID() string                             { return n.DocID }
func (n *apiNoteURL) Rev() string                            { return "" }
func (n *apiNoteURL) DocType() string                        { return consts.NotesURL }
func (n *apiNoteURL) Clone() couchdb.Doc                     { cloned := *n; return &cloned }
func (n *apiNoteURL) SetID(id string)                        { n.DocID = id }
func (n *apiNoteURL) SetRev(rev string)                      {}
func (n *apiNoteURL) Relationships() jsonapi.RelationshipMap { return nil }
func (n *apiNoteURL) Included() []jsonapi.Object             { return nil }
func (n *apiNoteURL) Links() *jsonapi.LinksList              { return nil }
func (n *apiNoteURL) Fetch(field string) []string            { return nil }

// Opener can be used to find the parameters for creating the URL where the
// note can be opened.
type Opener struct {
	inst      *instance.Instance
	file      *vfs.FileDoc
	sharing   *sharing.Sharing // can be nil
	code      string
	clientID  string
	memberKey string
}

// Open will return an Opener for the given file.
func Open(inst *instance.Instance, fileID string) (*Opener, error) {
	file, err := inst.VFS().FileByID(fileID)
	if err != nil {
		return nil, err
	}

	// Check that the file is a note
	if _, err := fromMetadata(file); err != nil {
		return nil, err
	}

	// Looks if the note is shared
	sharing, err := getSharing(inst, fileID)
	if err != nil {
		return nil, err
	}

	return &Opener{inst: inst, file: file, sharing: sharing}, nil
}

func getSharing(inst *instance.Instance, fileID string) (*sharing.Sharing, error) {
	sid := consts.Files + "/" + fileID
	var ref sharing.SharedRef
	if err := couchdb.GetDoc(inst, consts.Shared, sid, &ref); err != nil {
		if couchdb.IsNotFoundError(err) {
			return nil, nil
		}
		return nil, err
	}

	for sharingID, info := range ref.Infos {
		if info.Removed {
			continue
		}
		var sharing sharing.Sharing
		if err := couchdb.GetDoc(inst, consts.Sharings, sharingID, &sharing); err != nil {
			return nil, err
		}
		if sharing.Active {
			return &sharing, nil
		}
	}
	return nil, nil
}

// AddShareByLinkCode can be used to give a sharecode that can be used to open
// the note, when the note is in a directory shared by link.
func (o *Opener) AddShareByLinkCode(code string) {
	o.code = code
}

// CheckPermission takes the permission doc, and checks that the user has the
// right to open the note.
func (o *Opener) CheckPermission(pdoc *permission.Permission, sharingID string) error {
	// If a note is opened from a preview of a sharing, and nobody has accepted
	// the sharing until now, the io.cozy.shared document for the note has not
	// been created, and we need to fill the sharing by another way.
	if o.sharing == nil && pdoc.Type == permission.TypeSharePreview {
		parts := strings.SplitN(pdoc.SourceID, "/", 2)
		if len(parts) != 2 {
			return sharing.ErrInvalidSharing
		}
		sharingID := parts[1]
		var sharing sharing.Sharing
		if err := couchdb.GetDoc(o.inst, consts.Sharings, sharingID, &sharing); err != nil {
			return err
		}
		o.sharing = &sharing
		preview, err := permission.GetForSharePreview(o.inst, sharingID)
		if err != nil {
			return err
		}
		for k, v := range preview.Codes {
			if v == o.code {
				o.memberKey = k
			}
		}
	}

	// If a note is opened via a token for cozy-to-cozy sharing, then the note
	// must be in this sharing, or the stack should refuse to open the note.
	if sharingID != "" && o.sharing != nil && o.sharing.ID() == sharingID {
		o.clientID = pdoc.SourceID
		return nil
	}

	fs := o.inst.VFS()
	return vfs.Allows(fs, pdoc.Permissions, permission.GET, o.file)
}

// GetResult looks if the note can be opened locally or not, which code can be
// used in case of a shared note, and other parameters.. and returns the information.
func (o *Opener) GetResult(memberIndex int, readOnly bool) (jsonapi.Object, error) {
	var result *apiNoteURL
	var err error
	if o.shouldOpenLocally() {
		result, err = o.openLocalNote(memberIndex, readOnly)
	} else {
		result, err = o.openSharedNote()
	}
	if err != nil {
		return nil, err
	}

	// Enforce DocID and PublicName with local values
	result.DocID = o.file.ID()
	if name, err := o.inst.PublicName(); err == nil {
		result.PublicName = name
	}
	return result, nil
}

func (o *Opener) shouldOpenLocally() bool {
	if o.sharing == nil {
		return true
	}
	u, err := url.Parse(o.file.CozyMetadata.CreatedOn)
	if err != nil {
		return true
	}
	return o.inst.HasDomain(u.Host)
}

func (o *Opener) openLocalNote(memberIndex int, readOnly bool) (*apiNoteURL, error) {
	code, err := o.getSharecode(memberIndex, readOnly)
	if err != nil {
		return nil, err
	}
	doc := &apiNoteURL{
		NoteID:    o.file.ID(),
		Instance:  o.inst.ContextualDomain(),
		Sharecode: code,
	}
	switch config.GetConfig().Subdomains {
	case config.FlatSubdomains:
		doc.Subdomain = "flat"
	case config.NestedSubdomains:
		doc.Subdomain = "nested"
	}
	doc.Protocol = "https"
	if build.IsDevRelease() {
		doc.Protocol = "http"
	}
	return doc, nil
}

func (o *Opener) openSharedNote() (*apiNoteURL, error) {
	s := o.sharing
	var creds *sharing.Credentials
	var creator *sharing.Member
	var memberIndex int
	readOnly := s.ReadOnlyRules()

	if s.Owner {
		domain := o.file.CozyMetadata.CreatedOn
		for i, m := range s.Members {
			if i == 0 {
				continue // Skip the owner
			}
			if m.Instance == domain || m.Instance+"/" == domain {
				creds = &s.Credentials[i-1]
				creator = &s.Members[i]
			}
		}
		if o.clientID != "" && !readOnly {
			for i, c := range s.Credentials {
				if c.InboundClientID == o.clientID {
					memberIndex = i + 1
					readOnly = s.Members[i+1].ReadOnly
				}
			}
		}
	} else {
		creds = &s.Credentials[0]
		creator = &s.Members[0]
	}

	logger.WithNamespace("foobar").Warnf("creator.Status = %v", creator.Status)
	if creator == nil ||
		(creator.Status != sharing.MemberStatusReady && creator.Status != sharing.MemberStatusOwner) {
		// If the creator of the note is no longer in the sharing, the owner of
		// the sharing takes the lead, and if the sharing is revoked, any
		// member can edit the note on their instance.
		if o.clientID == "" {
			o.sharing = nil
		}
		return o.openLocalNote(memberIndex, readOnly)
	}

	xoredID := sharing.XorID(o.file.ID(), creds.XorKey)
	u, err := url.Parse(creator.Instance)
	if err != nil {
		return nil, err
	}
	opts := &request.Options{
		Method: http.MethodGet,
		Scheme: u.Scheme,
		Domain: u.Host,
		Path:   "/notes/" + xoredID + "/open",
		Queries: url.Values{
			"SharingID":   {s.ID()},
			"MemberIndex": {strconv.FormatInt(int64(memberIndex), 10)},
			"ReadOnly":    {strconv.FormatBool(readOnly)},
		},
		Headers: request.Headers{
			"Accept":        "application/vnd.api+json",
			"Authorization": "Bearer " + creds.AccessToken.AccessToken,
		},
	}
	res, err := request.Req(opts)
	if res != nil && res.StatusCode/100 == 4 {
		res, err = sharing.RefreshToken(o.inst, s, creator, creds, opts, nil)
	}
	if err != nil {
		return nil, sharing.ErrInternalServerError
	}
	defer res.Body.Close()
	var doc apiNoteURL
	if _, err := jsonapi.Bind(res.Body, &doc); err != nil {
		return nil, err
	}
	return &doc, nil
}

func (o *Opener) getSharecode(memberIndex int, readOnly bool) (string, error) {
	s := o.sharing
	if s == nil || (o.clientID == "" && o.memberKey == "") {
		return o.code, nil
	}

	var member *sharing.Member
	var err error
	if o.memberKey != "" {
		// Preview of a cozy-to-cozy sharing
		for i, m := range s.Members {
			if m.Instance == o.memberKey || m.Email == o.memberKey {
				member = &s.Members[i]
			}
		}
		if member == nil {
			return "", sharing.ErrMemberNotFound
		}
		if member.ReadOnly {
			readOnly = true
		} else {
			readOnly = s.ReadOnlyRules()
		}
	} else if s.Owner {
		member, err = s.FindMemberByInboundClientID(o.clientID)
		if err != nil {
			return "", err
		}
		if member.ReadOnly {
			readOnly = true
		} else {
			readOnly = s.ReadOnlyRules()
		}
	} else {
		// Trust the owner
		if memberIndex < 0 && memberIndex >= len(s.Members) {
			return "", sharing.ErrMemberNotFound
		}
		member = &s.Members[memberIndex]
	}

	if readOnly {
		return o.getPreviewCode(member)
	}
	return o.getInteractCode(member)
}

// getPreviewCode returns a sharecode that can be used for reading the note. It
// uses a share-preview token.
func (o *Opener) getPreviewCode(member *sharing.Member) (string, error) {
	var codes map[string]string
	preview, err := permission.GetForSharePreview(o.inst, o.sharing.ID())
	if err != nil {
		if couchdb.IsNotFoundError(err) {
			codes, err = o.sharing.CreatePreviewPermissions(o.inst)
		}
		if err != nil {
			return "", err
		}
	} else {
		codes = preview.Codes
	}

	for key, code := range codes {
		if key == member.Instance || key == member.Email {
			return code, nil
		}
	}

	return "", ErrInvalidFile
}

// getInteractCode returns a sharecode that can be use for reading and writing
// the note. It uses a share-interact token.
func (o *Opener) getInteractCode(member *sharing.Member) (string, error) {
	interact, err := permission.GetForShareInteract(o.inst, o.sharing.ID())
	if err != nil {
		if couchdb.IsNotFoundError(err) {
			return o.sharing.CreateInteractPermissions(o.inst, member)
		}
		return "", err
	}

	// If we already have a code for this member, let's use it
	for key, code := range interact.Codes {
		if key == member.Instance || key == member.Email {
			return code, nil
		}
	}

	// Else, create a code and add it to the permission doc
	key := member.Email
	if key == "" {
		key = member.Instance
	}
	code, err := o.inst.CreateShareCode(key)
	if err != nil {
		return "", err
	}
	interact.Codes[key] = code
	if err := couchdb.UpdateDoc(o.inst, interact); err != nil {
		return "", err
	}
	return code, nil
}
<|endoftext|>


<commit_before>package models

import (
	"encoding/json"
	"strconv"

	"github.com/hieven/go-instagram/constants"
	"github.com/hieven/go-instagram/utils"
	"github.com/parnurzeal/gorequest"
)

type Location struct {
	ExternalSource   string                `json:"external_source"`
	City             string                `json:"city"`
	Name             string                `json:"name"`
	FacebookPlacesID int                   `json:"facebook_places_id"`
	Address          string                `json:"address"`
	Lat              float64               `json:"lat"`
	Lng              float64               `json:"lng"`
	Pk               int                   `json:"pk"`
	Request          *gorequest.SuperAgent `json:"request"`
}

type mediaResponse struct {
	RankedItems []*Media `json:"ranked_items"`
	Items       []*Media `json:"media"`
}

func (location Location) GetRankedMedia() []*Media {
	url := constants.ROUTES.LocationFeed + strconv.Itoa(location.Pk) + "/"

	_, body, _ := utils.WrapRequest(
		location.Request.Get(url).
			Query("rank_token=" + utils.GenerateUUID()))

	var resp mediaResponse
	json.Unmarshal([]byte(body), &resp)

	return resp.RankedItems
}
<commit_msg>add `getRecentItems` of location<commit_after>package models

import (
	"encoding/json"
	"strconv"

	"github.com/hieven/go-instagram/constants"
	"github.com/hieven/go-instagram/utils"
	"github.com/parnurzeal/gorequest"
)

type Location struct {
	ExternalSource   string                `json:"external_source"`
	City             string                `json:"city"`
	Name             string                `json:"name"`
	FacebookPlacesID int64                 `json:"facebook_places_id"`
	Address          string                `json:"address"`
	Lat              float64               `json:"lat"`
	Lng              float64               `json:"lng"`
	Pk               int64                 `json:"pk"`
	Request          *gorequest.SuperAgent `json:"request"`
}

type mediaResponse struct {
	RankedItems []*Media `json:"ranked_items"`
	Items       []*Media `json:"items"`
}

func (location Location) GetRankedMedia() []*Media {
	url := constants.ROUTES.LocationFeed + strconv.FormatInt(location.Pk, 10) + "/"

	_, body, _ := utils.WrapRequest(
		location.Request.Get(url).
			Query("rank_token=" + utils.GenerateUUID()))

	var resp mediaResponse
	json.Unmarshal([]byte(body), &resp)

	return resp.RankedItems
}

func (location Location) GetRecentMedia() []*Media {
	url := constants.ROUTES.LocationFeed + strconv.FormatInt(location.Pk, 10) + "/"

	_, body, _ := utils.WrapRequest(
		location.Request.Get(url).
			Query("rank_token=" + utils.GenerateUUID()))

	var resp mediaResponse
	json.Unmarshal([]byte(body), &resp)

	return resp.Items
}
<|endoftext|>


<commit_before>package protobuf

import (
	"encoding/binary"
	"errors"
	"fmt"
	"github.com/golang/protobuf/proto"
	"github.com/name5566/leaf/chanrpc"
	"github.com/name5566/leaf/log"
	"math"
	"reflect"
)

// -------------------------
// | id | protobuf message |
// -------------------------
type Processor struct {
	littleEndian bool
	msgInfo      []*MsgInfo
	msgID        map[reflect.Type]uint16
}

type MsgInfo struct {
	msgType    reflect.Type
	msgRouter  *chanrpc.Server
	msgHandler MsgHandler
}

type MsgHandler func([]interface{})

func NewProcessor() *Processor {
	p := new(Processor)
	p.littleEndian = false
	p.msgID = make(map[reflect.Type]uint16)
	return p
}

// It's dangerous to call the method on routing or marshaling (unmarshaling)
func (p *Processor) SetByteOrder(littleEndian bool) {
	p.littleEndian = littleEndian
}

// It's dangerous to call the method on routing or marshaling (unmarshaling)
func (p *Processor) Register(msg proto.Message) uint16 {
	msgType := reflect.TypeOf(msg)
	if msgType == nil || msgType.Kind() != reflect.Ptr {
		log.Fatal("protobuf message pointer required")
	}
	if _, ok := p.msgID[msgType]; ok {
		log.Fatal("message %s is already registered", msgType)
	}
	if len(p.msgInfo) >= math.MaxUint16 {
		log.Fatal("too many protobuf messages (max = %v)", math.MaxUint16)
	}

	i := new(MsgInfo)
	i.msgType = msgType
	p.msgInfo = append(p.msgInfo, i)
	id := uint16(len(p.msgInfo) - 1)
	p.msgID[msgType] = id
	return id
}

// It's dangerous to call the method on routing or marshaling (unmarshaling)
func (p *Processor) SetRouter(msg proto.Message, msgRouter *chanrpc.Server) {
	msgType := reflect.TypeOf(msg)
	id, ok := p.msgID[msgType]
	if !ok {
		log.Fatal("message %s not registered", msgType)
	}

	p.msgInfo[id].msgRouter = msgRouter
}

// It's dangerous to call the method on routing or marshaling (unmarshaling)
func (p *Processor) SetHandler(msg proto.Message, msgHandler MsgHandler) {
	msgType := reflect.TypeOf(msg)
	id, ok := p.msgID[msgType]
	if !ok {
		log.Fatal("message %s not registered", msgType)
	}

	p.msgInfo[id].msgHandler = msgHandler
}

// goroutine safe
func (p *Processor) Route(msg interface{}, userData interface{}) error {
	msgType := reflect.TypeOf(msg)
	id, ok := p.msgID[msgType]
	if !ok {
		return fmt.Errorf("message %s not registered", msgType)
	}

	i := p.msgInfo[id]
	if i.msgHandler != nil {
		i.msgHandler([]interface{}{msg, userData})
	}
	if i.msgRouter != nil {
		i.msgRouter.Go(msgType, msg, userData)
	}
	return nil
}

// goroutine safe
func (p *Processor) Unmarshal(data []byte) (interface{}, error) {
	if len(data) < 2 {
		return nil, errors.New("protobuf data too short")
	}

	// id
	var id uint16
	if p.littleEndian {
		id = binary.LittleEndian.Uint16(data)
	} else {
		id = binary.BigEndian.Uint16(data)
	}

	// msg
	if id >= uint16(len(p.msgInfo)) {
		return nil, fmt.Errorf("message id %v not registered", id)
	}
	msg := reflect.New(p.msgInfo[id].msgType.Elem()).Interface()
	return msg, proto.UnmarshalMerge(data[2:], msg.(proto.Message))
}

// goroutine safe
func (p *Processor) Marshal(msg interface{}) ([][]byte, error) {
	msgType := reflect.TypeOf(msg)

	// id
	_id, ok := p.msgID[msgType]
	if !ok {
		err := fmt.Errorf("message %s not registered", msgType)
		return nil, err
	}

	id := make([]byte, 2)
	if p.littleEndian {
		binary.LittleEndian.PutUint16(id, _id)
	} else {
		binary.BigEndian.PutUint16(id, _id)
	}

	// data
	data, err := proto.Marshal(msg.(proto.Message))
	return [][]byte{id, data}, err
}

// goroutine safe
func (p *Processor) Range(f func(id uint16, t reflect.Type)) {
	for id, i := range p.msgInfo {
		f(uint16(id), i.msgType)
	}
}
<commit_msg>add raw handler.<commit_after>package protobuf

import (
	"encoding/binary"
	"errors"
	"fmt"
	"github.com/golang/protobuf/proto"
	"github.com/name5566/leaf/chanrpc"
	"github.com/name5566/leaf/log"
	"math"
	"reflect"
)

// -------------------------
// | id | protobuf message |
// -------------------------
type Processor struct {
	littleEndian bool
	msgInfo      []*MsgInfo
	msgID        map[reflect.Type]uint16
}

type MsgInfo struct {
	msgType       reflect.Type
	msgRouter     *chanrpc.Server
	msgHandler    MsgHandler
	msgRawHandler MsgHandler
}

type MsgHandler func([]interface{})

type MsgRaw struct {
	msgID      uint16
	msgRawData []byte
}

func NewProcessor() *Processor {
	p := new(Processor)
	p.littleEndian = false
	p.msgID = make(map[reflect.Type]uint16)
	return p
}

// It's dangerous to call the method on routing or marshaling (unmarshaling)
func (p *Processor) SetByteOrder(littleEndian bool) {
	p.littleEndian = littleEndian
}

// It's dangerous to call the method on routing or marshaling (unmarshaling)
func (p *Processor) Register(msg proto.Message) uint16 {
	msgType := reflect.TypeOf(msg)
	if msgType == nil || msgType.Kind() != reflect.Ptr {
		log.Fatal("protobuf message pointer required")
	}
	if _, ok := p.msgID[msgType]; ok {
		log.Fatal("message %s is already registered", msgType)
	}
	if len(p.msgInfo) >= math.MaxUint16 {
		log.Fatal("too many protobuf messages (max = %v)", math.MaxUint16)
	}

	i := new(MsgInfo)
	i.msgType = msgType
	p.msgInfo = append(p.msgInfo, i)
	id := uint16(len(p.msgInfo) - 1)
	p.msgID[msgType] = id
	return id
}

// It's dangerous to call the method on routing or marshaling (unmarshaling)
func (p *Processor) SetRouter(msg proto.Message, msgRouter *chanrpc.Server) {
	msgType := reflect.TypeOf(msg)
	id, ok := p.msgID[msgType]
	if !ok {
		log.Fatal("message %s not registered", msgType)
	}

	p.msgInfo[id].msgRouter = msgRouter
}

// It's dangerous to call the method on routing or marshaling (unmarshaling)
func (p *Processor) SetHandler(msg proto.Message, msgHandler MsgHandler) {
	msgType := reflect.TypeOf(msg)
	id, ok := p.msgID[msgType]
	if !ok {
		log.Fatal("message %s not registered", msgType)
	}

	p.msgInfo[id].msgHandler = msgHandler
}

// It's dangerous to call the method on routing or marshaling (unmarshaling)
func (p *Processor) SetRawHandler(id uint16, msgRawHandler MsgHandler) {
	if id >= uint16(len(p.msgInfo)) {
		log.Fatal("message id %v not registered", id)
	}

	p.msgInfo[id].msgRawHandler = msgRawHandler
}

// goroutine safe
func (p *Processor) Route(msg interface{}, userData interface{}) error {
	msgType := reflect.TypeOf(msg)

	// raw
	if msgRaw, ok := msg.(MsgRaw); ok {
		if msgRaw.msgID >= uint16(len(p.msgInfo)) {
			return fmt.Errorf("message id %v not registered", msgRaw.msgID)
		}
		i := p.msgInfo[msgRaw.msgID]
		if i.msgRawHandler != nil {
			i.msgRawHandler([]interface{}{msgRaw.msgID, msgRaw.msgRawData, userData})
		}
		return nil
	}

	// protobuf
	id, ok := p.msgID[msgType]
	if !ok {
		return fmt.Errorf("message %s not registered", msgType)
	}
	i := p.msgInfo[id]
	if i.msgHandler != nil {
		i.msgHandler([]interface{}{msg, userData})
	}
	if i.msgRouter != nil {
		i.msgRouter.Go(msgType, msg, userData)
	}
	return nil
}

// goroutine safe
func (p *Processor) Unmarshal(data []byte) (interface{}, error) {
	if len(data) < 2 {
		return nil, errors.New("protobuf data too short")
	}

	// id
	var id uint16
	if p.littleEndian {
		id = binary.LittleEndian.Uint16(data)
	} else {
		id = binary.BigEndian.Uint16(data)
	}
	if id >= uint16(len(p.msgInfo)) {
		return nil, fmt.Errorf("message id %v not registered", id)
	}

	// msg
	i := p.msgInfo[id]
	if i.msgRawHandler != nil {
		return MsgRaw{id, data[2:]}, nil
	} else {
		msg := reflect.New(i.msgType.Elem()).Interface()
		return msg, proto.UnmarshalMerge(data[2:], msg.(proto.Message))
	}
}

// goroutine safe
func (p *Processor) Marshal(msg interface{}) ([][]byte, error) {
	msgType := reflect.TypeOf(msg)

	// id
	_id, ok := p.msgID[msgType]
	if !ok {
		err := fmt.Errorf("message %s not registered", msgType)
		return nil, err
	}

	id := make([]byte, 2)
	if p.littleEndian {
		binary.LittleEndian.PutUint16(id, _id)
	} else {
		binary.BigEndian.PutUint16(id, _id)
	}

	// data
	data, err := proto.Marshal(msg.(proto.Message))
	return [][]byte{id, data}, err
}

// goroutine safe
func (p *Processor) Range(f func(id uint16, t reflect.Type)) {
	for id, i := range p.msgInfo {
		f(uint16(id), i.msgType)
	}
}
<|endoftext|>