package main import ( "errors" "io" "time" ipfsapi "github.com/ipfs/go-ipfs-api" "golang.org/x/net/context" ) type File struct { Name string `json:"name"` AbsolutePath string `json:"absolute_path"` Contents io.Reader `json:"-"` } type Filestore interface { Get(name string) (*File, error) Set(file *File) error Delete(file *File) error Exists(name string) (bool, error) } func NewIPFSFilestore(ipfs *ipfsapi.Shell, rootDir string) (*IPFSFilestore, error) { store := &IPFSFilestore{ ipfs: ipfs, rootDir: rootDir, } exists, err := store.Exists("") if err != nil { return nil, err } if !exists { err := ipfs.FilesMkdir(newContext(), rootDir) if err != nil { return nil, err } } return store, nil } type IPFSFilestore struct { ipfs *ipfsapi.Shell rootDir string } var ErrFileNotFound = errors.New("File not found") type ErrFailedFileExistsCheck struct { parent error } func (e *ErrFailedFileExistsCheck) Unwrap() error { return e.parent } func (e *ErrFailedFileExistsCheck) Error() string { msg := "Failed to determine whether file exists" if e.parent != nil { msg += ": " + e.parent.Error() } return msg } func newContext() context.Context { ctx, _ := context.WithTimeout(context.Background(), 10*time.Minute) return ctx } func (s *IPFSFilestore) Exists(name string) (bool, error) { ctx := newContext() _, err := s.ipfs.FilesStat(ctx, s.rootDir+"/"+name) if err != nil { if err.Error() == "files/stat: file does not exist" { return false, nil } return false, &ErrFailedFileExistsCheck{parent: err} } return true, nil } type FileReader struct { ipfs *ipfsapi.Shell file *File reader io.ReadCloser } func (r *FileReader) Read(p []byte) (int, error) { if r.reader == nil { var err error r.reader, err = r.ipfs.FilesRead(newContext(), r.file.AbsolutePath) if err != nil { return 0, err } } return r.reader.Read(p) } func (r *FileReader) Close() error { if r.reader == nil { return nil } return r.reader.Close() } func (s *IPFSFilestore) Get(name string) (*File, error) { exists, err := s.Exists(name) if err != nil { return nil, err } if !exists { return nil, ErrFileNotFound } file := &File{ Name: name, AbsolutePath: s.rootDir + "/" + name, } file.Contents = &FileReader{ ipfs: s.ipfs, file: file, } return file, nil } func (s *IPFSFilestore) Set(file *File) error { return s.ipfs.FilesWrite(newContext(), s.rootDir+"/"+file.Name, file.Contents) } func (s *IPFSFilestore) Delete(file *File) error { return s.ipfs.FilesRm(newContext(), file.AbsolutePath, true) }