func AddFile(from, to string, nocopy bool, makedir bool, overwrite bool) (string, error) {
	log.Println("Adding file from", from, "to", BasePath+to, "...")
	hash, err := IPFSAddFile(from, nocopy, false)
	if err != nil {
		return "", err
	}
	if makedir {
		toSplit := strings.Split(to, "/")
		parent := strings.Join(toSplit[:len(toSplit)-1], "/")
		if Verbose {
			log.Printf("Creating parent directory '%s' in MFS...\n", parent)
		}
		err = MakeDir(parent)
		if err != nil {
			return "", err
		}
	}
	if overwrite {
		if Verbose {
			log.Println("Removing existing file (if any)...")
		}
		RemoveFile(to)
	}
	if Verbose {
		log.Println("Adding file to mfs path:", BasePath+to)
	}
	_, err = doRequest(TimeoutTime, fmt.Sprintf(`files/cp?arg=%s&arg=%s`, "/ipfs/"+url.QueryEscape(hash.Hash), url.QueryEscape(BasePath+to)))
	if err != nil {
		if Verbose {
			log.Println("Error on files/cp:", err)
			log.Println("fpath:", from)
		}
		if HandleBadBlockError(err, from, nocopy) {
			log.Println("files/cp failure due to filestore, retrying (recursive)")
			AddFile(from, to, nocopy, makedir, overwrite)
		}
	}
	return hash.Hash, err
}
AddFile adds a file to the MFS relative to BasePath. from should be the full path to the file intended to be added.
If makedir is true, it'll create the directory it'll be placed in.
If overwrite is true, it'll perform an rm before copying to MFS.
package main
import (
	"bytes"
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"io/fs"
	"io/ioutil"
	"log"
	"mime/multipart"
	"net/http"
	"net/textproto"
	"net/url"
	"os"
	"path/filepath"
	"strings"
	"time"
)
const (
	KeySpace = "ipfs-sync."
	API      = "/api/v0/"
)
func findInStringSlice(slice []string, val string) int {
	for i, item := range slice {
		if item == val {
			return i
		}
	}
	return -1
}
func doRequest(timeout time.Duration, cmd string) (string, error) {
	var cancel context.CancelFunc
	ctx := context.Background()
	if timeout > 0 {
		ctx, cancel = context.WithTimeout(ctx, timeout)
		defer cancel()
	}
	c := &http.Client{}
	req, err := http.NewRequestWithContext(ctx, "POST", EndPoint+API+cmd, nil)
	if err != nil {
		return "", err
	}
	resp, err := c.Do(req)
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return "", err
	}
	errStruct := new(ErrorStruct)
	err = json.Unmarshal(body, errStruct)
	if err == nil {
		if errStruct.Error() != "" {
			return string(body), errStruct
		}
	}
	return string(body), nil
}
type HashStruct struct {
	Hash string
}
func GetFileCID(filePath string) string {
	out, _ := doRequest(TimeoutTime, "files/stat?hash=true&arg="+url.QueryEscape(BasePath+filePath))
	fStat := new(HashStruct)
	err := json.Unmarshal([]byte(out), &fStat)
	if err != nil {
		return ""
	}
	return fStat.Hash
}
func RemoveFile(fpath string) error {
	_, err := doRequest(TimeoutTime, fmt.Sprintf(`files/rm?arg=%s&force=true`, url.QueryEscape(BasePath+fpath)))
	return err
}
func MakeDir(path string) error {
	_, err := doRequest(TimeoutTime, fmt.Sprintf(`files/mkdir?arg=%s&parents=true`, url.QueryEscape(BasePath+path)))
	return err
}
func filePathWalkDir(root string) ([]string, error) {
	var files []string
	err := filepath.WalkDir(root, func(path string, info fs.DirEntry, err error) error {
		if info == nil {
			return errors.New(fmt.Sprintf("cannot access '%s' for crawling", path))
		}
		if !info.IsDir() {
			filePathSplit := strings.Split(path, string(os.PathSeparator))
			if IgnoreHidden && filePathSplit[len(filePathSplit)-1][0] == '.' {
				return nil
			}
			files = append(files, path)
		} else {
			dirPathSplit := strings.Split(path, string(os.PathSeparator))
			if IgnoreHidden && len(dirPathSplit[len(dirPathSplit)-1]) > 0 && dirPathSplit[len(dirPathSplit)-1][0] == '.' {
				return filepath.SkipDir
			}
		}
		return nil
	})
	return files, err
}
func AddDir(path string, nocopy bool, pin bool, estuary bool) (string, error) {
	pathSplit := strings.Split(path, string(os.PathSeparator))
	dirName := pathSplit[len(pathSplit)-2]
	files, err := filePathWalkDir(path)
	if err != nil {
		return "", err
	}
	localDirs := make(map[string]bool)
	for _, file := range files {
		filePathSplit := strings.Split(file, string(os.PathSeparator))
		if IgnoreHidden && filePathSplit[len(filePathSplit)-1][0] == '.' {
			continue
		}
		splitName := strings.Split(file, ".")
		if findInStringSlice(Ignore, splitName[len(splitName)-1]) > -1 {
			continue
		}
		parentDir := strings.Join(filePathSplit[:len(filePathSplit)-1], string(os.PathSeparator))
		makeDir := !localDirs[parentDir]
		if makeDir {
			localDirs[parentDir] = true
		}
		mfsPath := file[len(path):]
		if os.PathSeparator != '/' {
			mfsPath = strings.ReplaceAll(mfsPath, string(os.PathSeparator), "/")
		}
		_, err := AddFile(file, dirName+"/"+mfsPath, nocopy, makeDir, false)
		if err != nil {
			log.Println("Error adding file:", err)
		}
	}
	cid := GetFileCID(dirName)
	if pin {
		err := Pin(cid)
		log.Println("Error pinning", dirName, ":", err)
	}
	if estuary {
		if err := PinEstuary(cid, dirName); err != nil {
			log.Println("Error pinning to Estuary:", err)
		}
	}
	return cid, err
}
func IPFSAddFile(fpath string, nocopy, onlyhash bool) (*HashStruct, error) {
	client := http.Client{}
	f, err := os.Open(fpath)
	if err != nil {
		return nil, err
	}
	pr, pw := io.Pipe()
	writer := multipart.NewWriter(pw)
	defer pr.Close()
	req, err := http.NewRequest("POST", EndPoint+API+fmt.Sprintf(`add?nocopy=%t&pin=false&quieter=true&only-hash=%t`, nocopy, onlyhash), pr)
	if err != nil {
		return nil, err
	}
	req.Header.Add("Content-Type", writer.FormDataContentType())
	go func() {
		defer f.Close()
		defer writer.Close()
		h := make(textproto.MIMEHeader)
		h.Set("Abspath", fpath)
		h.Set("Content-Disposition", fmt.Sprintf(`form-data; name="%s"; filename="%s"`, "file", url.QueryEscape(f.Name())))
		h.Set("Content-Type", "application/octet-stream")
		part, err := writer.CreatePart(h)
		if err != nil {
			pw.CloseWithError(err)
			return
		}
		if Verbose {
			log.Println("Generating file headers...")
		}
		_, err = io.Copy(part, f)
		pw.CloseWithError(err)
	}()
	if Verbose {
		log.Println("Doing add request...")
	}
	resp, err := client.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	var hash HashStruct
	err = json.NewDecoder(resp.Body).Decode(&hash)
	if Verbose {
		log.Println("File hash:", hash.Hash)
	}
	return &hash, err
}