cloudfs

package module
v0.0.0-...-9530319 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 19, 2026 License: MIT Imports: 10 Imported by: 0

README

CloudFS

CloudFS is a Go library that exposes local disks and multiple cloud storage providers through one filesystem-like interface.

It provides:

  • A common cloudfs.FS interface for listing, reading, writing, moving, copying, renaming, and deleting files.
  • Driver packages for Local, FTP, SFTP, S3, SMB, WebDAV, Google Drive, OneDrive, GitHub, and other storage providers.
  • Middleware wrappers for prefix mapping, caching, rate limiting, compression, encryption, and custom hooks.

Install

go get github.com/honmaple/cloudfs

Import only the driver you need:

import "github.com/honmaple/cloudfs/driver/webdav"

Or import all built-in drivers for dynamic creation through driver.New:

import _ "github.com/honmaple/cloudfs/driver/all"

Quick Start

package main

import (
    "context"
    "io"
	"strings"

	"github.com/honmaple/cloudfs/driver/local"
)

func main() {
	fs, err := local.New(&local.Option{
		Path: "/tmp/cloudfs",
	})
	if err != nil {
		panic(err)
	}
	defer fs.Close()

	ctx := context.Background()

	if err := fs.MakeDir(ctx, "/docs"); err != nil {
		panic(err)
	}

	w, err := fs.Create(ctx, "/docs/hello.txt")
	if err != nil {
		panic(err)
	}
	if _, err := io.Copy(w, strings.NewReader("hello cloudfs")); err != nil {
		_ = w.Close()
		panic(err)
	}
	if err := w.Close(); err != nil {
		panic(err)
	}

	files, err := fs.List(ctx, "/docs")
	if err != nil {
		panic(err)
	}
	for _, file := range files {
		println(file.Path(), file.Name(), file.Size())
	}

	r, err := fs.Open(ctx, "/docs/hello.txt")
	if err != nil {
		panic(err)
	}
	defer r.Close()
}

Create a Driver

You can create a driver directly through its package:

fs, err := webdav.New(&webdav.Option{
	Endpoint: "https://example.com/dav",
	Username: "user",
	Password: "pass",
})

You can also create drivers dynamically. Import driver once for side-effect registration:

import (
	"github.com/honmaple/cloudfs/driver"
	_ "github.com/honmaple/cloudfs/driver/all"
)

fs, err := driver.New("webdav", map[string]any{
	"endpoint": "https://example.com/dav",
	"username": "user",
	"password": "pass",
})

Use driver.NewFromString when the configuration is already JSON:

fs, err := driver.NewFromString("local", `{"path":"/tmp/cloudfs"}`)

Useful helpers:

ok := driver.Exists("s3")
err := driver.VerifyOption("s3", `{"endpoint":"https://s3.example.com","bucket":"files"}`)

Common Operations

All paths use slash-separated absolute-style paths such as /, /dir, and /dir/file.txt.

ctx := context.Background()

files, err := fs.List(ctx, "/")
info, err := fs.Stat(ctx, "/file.txt")
err = fs.MakeDir(ctx, "/new-dir")
err = fs.Rename(ctx, "/file.txt", "new-name.txt")
err = fs.Move(ctx, "/new-name.txt", "/new-dir")
err = fs.Copy(ctx, "/new-dir/new-name.txt", "/")
err = fs.Remove(ctx, "/new-dir/new-name.txt")

List and Stat return cloudfs.FileInfo, which follows io/fs.FileInfo and adds Path(), Type(), and ExtraInfo() for driver-specific metadata. Use cloudfs.Entry when you need to build a cloudfs.FileInfo manually.

Reading supports io.Reader, io.Seeker, and io.Closer:

r, err := fs.Open(ctx, "/file.txt")
if err != nil {
	return err
}
defer r.Close()

_, _ = r.Seek(10, io.SeekStart)

Writing returns an io.WriteCloser. Always close it to finish the upload:

w, err := fs.Create(ctx, "/file.txt")
if err != nil {
	return err
}
_, err = io.Copy(w, source)
if closeErr := w.Close(); err == nil {
	err = closeErr
}

Drivers

Driver Name List Mkdir Rename Move Copy Remove Upload Download
Local local yes yes yes yes yes yes yes yes
FTP ftp yes yes yes yes yes yes yes yes
SFTP sftp yes yes yes yes yes yes yes yes
S3 s3 yes yes yes yes yes yes yes yes
SMB smb yes yes yes yes yes yes yes yes
WebDAV webdav yes yes yes yes yes yes yes yes
Foxel foxel yes yes yes yes yes yes yes yes
Openlist openlist yes yes yes yes yes yes yes yes
Upyun upyun yes yes yes yes yes yes yes yes
Google Drive gdrive yes yes yes yes yes yes yes yes
OneDrive onedrive yes yes yes yes yes yes yes yes
115 pan115 yes yes yes yes yes yes no yes
Quark quark yes yes yes yes no yes no yes
GitHub github yes no no no no no no yes
GitHub Release github-release yes no no no no no no yes

Driver Options

Common examples:

// Local
fs, err := driver.New("local", map[string]any{
	"path": "/data/files",
})

// WebDAV
fs, err = driver.New("webdav", map[string]any{
	"endpoint": "https://example.com/dav",
	"username": "user",
	"password": "pass",
})

// S3-compatible storage
fs, err = driver.New("s3", map[string]any{
	"endpoint": "https://s3.example.com",
	"bucket": "files",
	"region": "us-east-1",
	"access_key": "key",
	"secret_key": "secret",
	"force_path_style": true,
})

Google Drive supports credentials JSON, a credentials file, an access token, or Application Default Credentials from the Google client library:

fs, err := driver.New("gdrive", map[string]any{
	"credentials_file": "/path/to/service-account.json",
	"root_id": "root",
	"export_mime_type": "application/pdf",
})

Useful Google Drive options:

  • credentials: service account or OAuth credentials JSON
  • credentials_file: path to credentials JSON
  • access_token: OAuth access token
  • root_id: root folder ID, defaults to root
  • shared_drive_id: shared drive ID
  • export_mime_type: export MIME type for Google Workspace files
  • supports_all_drives: enable all-drives calls

OneDrive uses Microsoft Graph and requires an OAuth access token:

fs, err := driver.New("onedrive", map[string]any{
	"access_token": "token",
})

Useful OneDrive options:

  • access_token: Microsoft Graph OAuth access token
  • endpoint: Graph endpoint, defaults to https://graph.microsoft.com/v1.0
  • drive_id: target a specific drive
  • user_id: target a user's default drive
  • root_id: root item ID, defaults to root
  • copy_timeout: maximum time to wait for async copy operations

Middlewares

Middlewares wrap an existing cloudfs.FS. They are applied in the order passed to cloudfs.New.

wrapped, err := cloudfs.New(
	fs,
	middleware.PrefixFS("/tenant-a"),
	middleware.CacheFS(&middleware.CacheOption{}),
)
PrefixFS

Maps all user-visible paths under a backend prefix.

fs, err = cloudfs.New(fs, middleware.PrefixFS("/storage/root"))
CacheFS

Caches directory listings and invalidates affected parent directories on writes. ExpireTime is in seconds; default is 60.

fs, err = cloudfs.New(fs, middleware.CacheFS(&middleware.CacheOption{
	ExpireTime: 60,
}))
RateLimitFS

Limits operation frequency.

fs, err = cloudfs.New(fs, middleware.RateLimitFS(&middleware.RateLimitOption{
	Wait: true,
	Burst: 30,
	Limit: time.Second,
}))
CompressFS

Compresses content on write and decompresses it on read. File names are not changed by this middleware.

fs, err = cloudfs.New(fs, middleware.CompressFS(&middleware.CompressOption{}))
EncryptFS

Encrypts file content and can optionally encrypt directory and file names.

fs, err = cloudfs.New(fs, middleware.EncryptFS(&middleware.EncryptOption{
	Password: "secret",
	DirName: false,
	FileName: true,
}))
HookFS

Use HookFS when you need custom path or file metadata rewriting.

fs, err = cloudfs.New(fs, middleware.HookFS(&middleware.HookOption{
	PathFn: func(path string) string {
		return "/backend" + path
	},
}))

Add a New Driver

A driver implements cloudfs.FS, usually embeds cloudfs.BaseFS, validates its Option, and registers itself in init.

type Option struct {
	Token string `json:"token" validate:"required"`
}

func (opt *Option) NewFS() (cloudfs.FS, error) {
	return New(opt)
}

func init() {
	driver.Register("example", func() driver.Option {
		return &Option{}
	})
}

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrOption         = errors.New("driver's option err")
	ErrNotSupport     = errors.New("operate not support")
	ErrSrcNotExist    = errors.New("src is not exists")
	ErrDstNotExist    = errors.New("dst is not exists")
	ErrDstIsExist     = errors.New("dst already exists")
	ErrDriverNotExist = errors.New("driver is not exists")
	ErrOpenDirectory  = errors.New("can't open a directory")
)

Functions

func Copy

func Copy(ctx context.Context, srcFS FS, src, dst string, opts ...map[string]any) error

func CopyDir

func CopyDir(ctx context.Context, srcFS FS, src, dst string) error

func CopyFile

func CopyFile(ctx context.Context, srcFS FS, src, dst string) error

func UnderlyingError

func UnderlyingError(err error) error

func WalkDir

func WalkDir(ctx context.Context, srcFS FS, root string, walkDirFn WalkDirFunc) error

Types

type BaseFS

type BaseFS struct{}

func (BaseFS) Close

func (BaseFS) Close() error

func (BaseFS) Copy

func (BaseFS) Create

func (BaseFS) List

func (BaseFS) MakeDir

func (BaseFS) MakeDir(context.Context, string) error

func (BaseFS) Move

func (BaseFS) Open

func (BaseFS) Open(context.Context, string) (File, error)

func (BaseFS) Remove

func (BaseFS) Remove(context.Context, string) error

func (BaseFS) Rename

func (BaseFS) Stat

type CopyOption

type CopyOption map[string]any

type Entry

type Entry struct {
	Name      string
	Type      string
	Size      int64
	Path      string
	Mode      fs.FileMode
	IsDir     bool
	ModTime   time.Time
	ExtraInfo map[string]any
	Sys       any
}

func (*Entry) FileInfo

func (f *Entry) FileInfo() FileInfo

type FS

func New

func New(fs FS, fns ...WrapFunc) (FS, error)

type File

type File interface {
	io.Seeker
	io.ReadCloser
}

func NewFile

func NewFile(size int64, rangeFunc func(int64, int64) (io.ReadCloser, error)) (File, error)

type FileInfo

type FileInfo interface {
	fs.FileInfo
	Type() string
	Path() string
	ExtraInfo() map[string]any
}

func NewFileInfo

func NewFileInfo(info fs.FileInfo, opts ...func(*Entry)) FileInfo

type FileWriter

type FileWriter interface {
	io.WriteCloser
}

type ListOption

type ListOption map[string]any

type Option

type Option struct {
	*viper.Viper
}

func ListOptions

func ListOptions(opts ...ListOption) *Option

func NewOption

func NewOption(opts ...map[string]any) *Option

type WalkDirFunc

type WalkDirFunc func(string, FileInfo, error) error

type WrapFunc

type WrapFunc func(FS) (FS, error)

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL