<commit_before>package main

import (
	"context"
	"flag"
	"fmt"
	"log"
	"os"

	"github.com/hnakamur/rdirsync"

	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials"
)

var name = "client"

const globalUsage = `Usage: %s <subcommand> [options] srcDirOrFile destDirOrFile

This is an example client command to synchronize directories and files between
the localhost and a remote server.

subcommands:
    send       send a local directory or file to the remote server.
    fetch      fetch a remote directory or file to the localhost.

`

func main() {
	os.Exit(run())
}

func run() int {
	flag.Usage = func() {
		fmt.Printf(globalUsage, name)
		flag.PrintDefaults()
	}
	flag.Parse()

	args := flag.Args()
	if len(args) == 0 {
		flag.Usage()
		return 1
	}

	switch args[0] {
	case "send":
		return handleSendCommand(args[1:])
	case "fetch":
		return handleFetchCommand(args[1:])
	default:
		flag.Usage()
		return 1
	}
}

type options struct {
	// grpc dial options
	serverAddr         string
	serverHostOverride string
	enableTLS          bool
	caFile             string

	// rdirsync options
	bufSize                 int
	maxEntriesPerReadDirRPC int
	keepDeletedFiles        bool
	syncOwnerAndGroup       bool
	syncModTime             bool
	updateOnly              bool
}

func parseOptions(subcommand, usage string, args []string) (*flag.FlagSet, *options) {
	fs := flag.NewFlagSet(subcommand, flag.ExitOnError)
	fs.Usage = func() {
		fmt.Printf(usage, name)
		fs.PrintDefaults()
	}

	var opts options
	fs.StringVar(&opts.serverAddr, "server", "127.0.0.1:10000", "server address to connect")
	fs.StringVar(&opts.serverHostOverride, "hostname", "grpc.example.com", "The server name use to verify the hostname returned by TLS handshake")
	fs.BoolVar(&opts.enableTLS, "tls", false, "enable TLS")
	fs.StringVar(&opts.caFile, "cafile", "cacert.pem", "The file containning the CA root cert file")
	fs.IntVar(&opts.bufSize, "bufsize", 64*1024, "buffer size for reading a file")
	fs.IntVar(&opts.maxEntriesPerReadDirRPC, "at-most-count", 1024, "at most file info count per readdir rpc")
	fs.BoolVar(&opts.keepDeletedFiles, "keep-deleted-files", false, "wether or not keep deleted files")
	fs.BoolVar(&opts.syncOwnerAndGroup, "o", false, "preseve owner and group (super-user only)")
	fs.BoolVar(&opts.syncModTime, "t", false, "preserve modification times")
	fs.BoolVar(&opts.updateOnly, "u", false, "skip files that are newer on the receiver")
	fs.Parse(args)
	return fs, &opts
}

func (o *options) buildGrpcOptions() []grpc.DialOption {
	var opts []grpc.DialOption
	if o.enableTLS {
		var sn string
		if o.serverHostOverride != "" {
			sn = o.serverHostOverride
		}
		var creds credentials.TransportCredentials
		if o.caFile != "" {
			var err error
			creds, err = credentials.NewClientTLSFromFile(o.caFile, sn)
			if err != nil {
				log.Fatalf("Failed to create TLS credentials %v", err)
			}
		} else {
			creds = credentials.NewClientTLSFromCert(nil, sn)
		}
		opts = append(opts, grpc.WithTransportCredentials(creds))
	} else {
		opts = append(opts, grpc.WithInsecure())
	}
	return opts
}

func (o *options) buildRDirSyncClientOptions() []rdirsync.ClientOption {
	return []rdirsync.ClientOption{
		rdirsync.SetBufSize(o.bufSize),
		rdirsync.SetMaxEntriesPerReadDirRPC(o.maxEntriesPerReadDirRPC),
		rdirsync.SetKeepDeletedFiles(o.keepDeletedFiles),
		rdirsync.SetSyncOwnerAndGroup(o.syncOwnerAndGroup),
		rdirsync.SetSyncModTime(o.syncModTime),
		rdirsync.SetUpdateOnly(o.updateOnly),
	}
}

const fetchUsage = `Usage: %s fetch [options] remoteDestDirOrFile localSrcDirOrFile

options:
`

func handleFetchCommand(args []string) int {
	fs, opts := parseOptions("fetch", fetchUsage, args)
	if fs.NArg() != 2 {
		fs.Usage()
		return 1
	}

	remotePath := fs.Arg(0)
	localPath := fs.Arg(1)

	conn, err := grpc.Dial(opts.serverAddr, opts.buildGrpcOptions()...)
	if err != nil {
		fmt.Printf("fail to connect to server: %+v", err)
		return 1
	}<commit_msg>Add workers option to client command<commit_after>package main

import (
	"context"
	"flag"
	"fmt"
	"log"
	"os"

	"github.com/hnakamur/rdirsync"

	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials"
)

var name = "client"

const globalUsage = `Usage: %s <subcommand> [options] srcDirOrFile destDirOrFile

This is an example client command to synchronize directories and files between
the localhost and a remote server.

subcommands:
    send       send a local directory or file to the remote server.
    fetch      fetch a remote directory or file to the localhost.

`

func main() {
	os.Exit(run())
}

func run() int {
	flag.Usage = func() {
		fmt.Printf(globalUsage, name)
		flag.PrintDefaults()
	}
	flag.Parse()

	args := flag.Args()
	if len(args) == 0 {
		flag.Usage()
		return 1
	}

	switch args[0] {
	case "send":
		return handleSendCommand(args[1:])
	case "fetch":
		return handleFetchCommand(args[1:])
	default:
		flag.Usage()
		return 1
	}
}

type options struct {
	// grpc dial options
	serverAddr         string
	serverHostOverride string
	enableTLS          bool
	caFile             string

	// rdirsync options
	bufSize                 int
	maxEntriesPerReadDirRPC int
	keepDeletedFiles        bool
	syncOwnerAndGroup       bool
	syncModTime             bool
	updateOnly              bool
	workers                 int
}

func parseOptions(subcommand, usage string, args []string) (*flag.FlagSet, *options) {
	fs := flag.NewFlagSet(subcommand, flag.ExitOnError)
	fs.Usage = func() {
		fmt.Printf(usage, name)
		fs.PrintDefaults()
	}

	var opts options
	fs.StringVar(&opts.serverAddr, "server", "127.0.0.1:10000", "server address to connect")
	fs.StringVar(&opts.serverHostOverride, "hostname", "grpc.example.com", "The server name use to verify the hostname returned by TLS handshake")
	fs.BoolVar(&opts.enableTLS, "tls", false, "enable TLS")
	fs.StringVar(&opts.caFile, "cafile", "cacert.pem", "The file containning the CA root cert file")
	fs.IntVar(&opts.bufSize, "bufsize", 64*1024, "buffer size for reading a file")
	fs.IntVar(&opts.maxEntriesPerReadDirRPC, "at-most-count", 1024, "at most file info count per readdir rpc")
	fs.BoolVar(&opts.keepDeletedFiles, "keep-deleted-files", false, "wether or not keep deleted files")
	fs.BoolVar(&opts.syncOwnerAndGroup, "o", false, "preseve owner and group (super-user only)")
	fs.BoolVar(&opts.syncModTime, "t", false, "preserve modification times")
	fs.BoolVar(&opts.updateOnly, "u", false, "skip files that are newer on the receiver")
	fs.IntVar(&opts.workers, "workers", 4, "worker count")
	fs.Parse(args)
	return fs, &opts
}

func (o *options) buildGrpcOptions() []grpc.DialOption {
	var opts []grpc.DialOption
	if o.enableTLS {
		var sn string
		if o.serverHostOverride != "" {
			sn = o.serverHostOverride
		}
		var creds credentials.TransportCredentials
		if o.caFile != "" {
			var err error
			creds, err = credentials.NewClientTLSFromFile(o.caFile, sn)
			if err != nil {
				log.Fatalf("Failed to create TLS credentials %v", err)
			}
		} else {
			creds = credentials.NewClientTLSFromCert(nil, sn)
		}
		opts = append(opts, grpc.WithTransportCredentials(creds))
	} else {
		opts = append(opts, grpc.WithInsecure())
	}
	return opts
}

func (o *options) buildRDirSyncClientOptions() []rdirsync.ClientOption {
	return []rdirsync.ClientOption{
		rdirsync.SetBufSize(o.bufSize),
		rdirsync.SetMaxEntriesPerReadDirRPC(o.maxEntriesPerReadDirRPC),
		rdirsync.SetKeepDeletedFiles(o.keepDeletedFiles),
		rdirsync.SetSyncOwnerAndGroup(o.syncOwnerAndGroup),
		rdirsync.SetSyncModTime(o.syncModTime),
		rdirsync.SetUpdateOnly(o.updateOnly),
		rdirsync.SetFileWorkerCount(o.workers),
	}
}

const fetchUsage = `Usage: %s fetch [options] remoteDestDirOrFile localSrcDirOrFile

options:
`

func handleFetchCommand(args []string) int {
	fs, opts := parseOptions("fetch", fetchUsage, args)
	if fs.NArg() != 2 {
		fs.Usage()
		return 1
	}

	remotePath := fs.Arg(0)
	localPath := fs.Arg(1)

	conn, err := grpc.Dial(opts.serverAddr, opts.buildGrpcOptions()...)
	if err != nil {
		fmt.Printf("fail to connect to server: %+v", err)
		return 1
	}
{'fraction_non_alphanumeric': 0.12320099255583127, 'fraction_numerical': 0.008064516129032258, 'mean_word_length': 4.013059701492537, 'pattern_counts': {'":': 4, '<': 5, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 2, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7203895', 'n_tokens_mistral': 2898, 'n_tokens_neox': 2758, 'n_words': 711}