func checkFile(db *bolt.DB, file os.DirEntry, path, name string, ignore bool, wg *sync.WaitGroup, sem chan struct{}) {
	defer func() {
		wg.Done()
		<-sem
	}()
	sem <- struct{}{}
	name = fmt.Sprintf("%s/%s", name, file.Name())
	path = filepath.Join(path, file.Name())
	if !file.IsDir() {
		if err := storeFile(db, path, name); err != nil {
			fmt.Fprintln(os.Stderr, "error:", err)
		}
		return
	}
	if ignore {
		return
	}
	subdir, err := os.ReadDir(path)
	if err != nil {
		fmt.Fprintf(os.Stderr, "error: reading directory: %v\n", err)
		return
	}
	if len(subdir) != 0 {
		wg.Add(len(subdir))
		go walkDir(db, subdir, path, name, ignore, wg, sem)
	}
}
checkFile checks if the item is a file or a folder.

If the item is a file, it stores it.

If it's a folder it repeats the process until there are no left files to store.

Errors are not returned but logged.
package add
import (
	"bufio"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"strings"
	"sync"
	"time"
	"github.com/GGP1/kure/auth"
	cmdutil "github.com/GGP1/kure/commands"
	"github.com/GGP1/kure/db/file"
	"github.com/GGP1/kure/pb"
	"github.com/pkg/errors"
	"github.com/spf13/cobra"
	bolt "go.etcd.io/bbolt"
)
var example = `
* Add a new file
kure file add Sample -p path/to/file
* Add a note
kure file add Sample -n
* Add a folder and all its subfolders, limiting goroutine number to 40
kure file add Sample -p path/to/folder -s 40
* Add files from a folder, ignoring subfolders
kure file add Sample -p path/to/folder -i`
type addOptions struct {
	path         string
	note, ignore bool
	semaphore    uint32
}
func NewCmd(db *bolt.DB, r io.Reader) *cobra.Command {
	opts := addOptions{}
	cmd := &cobra.Command{
		Use:   "add <name>",
		Short: "Add files to the database",
		Long: `Add files to the database. As they are stored in a database, the whole file is read into memory, please have this into account when adding new ones.
Path to a file must include its extension (in case it has one).
The user can specify a path to a folder as well, on this occasion, Kure will iterate over all the files in the folder and potential subfolders (if the -i flag is false) and store them into the database with the name "name/subfolders/filename". Empty folders will be skipped.`,
		Aliases: []string{"new"},
		Example: example,
		Args:    cmdutil.MustNotExist(db, cmdutil.File),
		PreRunE: auth.Login(db),
		RunE:    runAdd(db, r, &opts),
		PostRun: func(cmd *cobra.Command, args []string) {
			opts = addOptions{
				semaphore: 50,
			}
		},
	}
	f := cmd.Flags()
	f.BoolVarP(&opts.ignore, "ignore", "i", false, "ignore subfolders")
	f.StringVarP(&opts.path, "path", "p", "", "path to the file/folder")
	f.BoolVarP(&opts.note, "note", "n", false, "add a note")
	f.Uint32VarP(&opts.semaphore, "semaphore", "s", 50, "maximum number of goroutines running concurrently")
	return cmd
}
func runAdd(db *bolt.DB, r io.Reader, opts *addOptions) cmdutil.RunEFunc {
	return func(cmd *cobra.Command, args []string) error {
		name := strings.Join(args, " ")
		name = cmdutil.NormalizeName(name)
		if opts.note {
			return addNote(db, r, name)
		}
		if opts.semaphore < 1 {
			return errors.New("invalid semaphore quantity")
		}
		if filepath.Ext(name) == "" && opts.path != "" {
			name += filepath.Ext(opts.path)
		}
		dir, err := os.ReadDir(opts.path)
		if err != nil {
			return storeFile(db, opts.path, name)
		}
		if len(dir) == 0 {
			return errors.Errorf("%q directory is empty", filepath.Base(opts.path))
		}
		var wg sync.WaitGroup
		sem := make(chan struct{}, opts.semaphore)
		wg.Add(len(dir))
		walkDir(db, dir, opts.path, name, opts.ignore, &wg, sem)
		wg.Wait()
		return nil
	}
}
func walkDir(db *bolt.DB, dir []os.DirEntry, path, name string, ignore bool, wg *sync.WaitGroup, sem chan struct{}) {
	for _, f := range dir {
		if !f.IsDir() && !f.Type().IsRegular() {
			wg.Done()
			continue
		}
		go checkFile(db, f, path, name, ignore, wg, sem)
	}
}