You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

146 lines
2.6 KiB
Go

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)
}