shithub: jirafs

ref: 37c59d74571b90a623211a7ef11cf9ce2d90529b
dir: /files.go/

View raw version
package main

import (
	"errors"
	"fmt"
	"log"
	"strings"
	"time"

	"github.com/joushou/qp"
	"github.com/joushou/qptools/fileserver/trees"
)

type jiraWalker interface {
	Walk(jc *Client, name string) (trees.File, error)
}

type jiraLister interface {
	List(jc *Client) ([]qp.Stat, error)
}

type jiraRemover interface {
	Remove(jc *Client, name string) error
}

// JiraDir is a convenience wrapper for dynamic directory hooks.
type JiraDir struct {
	thing  interface{}
	client *Client
	*trees.SyntheticDir
}

func (jd *JiraDir) Walk(user, name string) (trees.File, error) {

	if f, ok := jd.thing.(jiraWalker); ok {
		return f.Walk(jd.client, name)
	}
	if f, ok := jd.thing.(trees.Dir); ok {
		return f.Walk(user, name)
	}

	return nil, trees.ErrPermissionDenied
}

func (jd *JiraDir) List(user string) ([]qp.Stat, error) {
	if f, ok := jd.thing.(jiraLister); ok {
		return f.List(jd.client)
	}
	if f, ok := jd.thing.(trees.Lister); ok {
		return f.List(user)
	}

	return nil, trees.ErrPermissionDenied
}

func (jd *JiraDir) Remove(user, name string) error {
	if f, ok := jd.thing.(jiraRemover); ok {
		return f.Remove(jd.client, name)
	}
	if f, ok := jd.thing.(trees.Dir); ok {
		return f.Remove(user, name)
	}

	return trees.ErrPermissionDenied
}

func (jd *JiraDir) Create(user, name string, perms qp.FileMode) (trees.File, error) {
	return nil, trees.ErrPermissionDenied
}

func (jd *JiraDir) Open(user string, mode qp.OpenMode) (trees.ReadWriteAtCloser, error) {
	if !jd.CanOpen(user, mode) {
		return nil, errors.New("access denied")
	}

	jd.Lock()
	defer jd.Unlock()
	jd.Atime = time.Now()
	jd.Opens++
	return &trees.ListHandle{
		Dir:  jd,
		User: user,
	}, nil
}

func NewJiraDir(name string, perm qp.FileMode, user, group string, jc *Client, thing interface{}) (*JiraDir, error) {
	switch thing.(type) {
	case trees.Dir, jiraWalker, jiraLister, jiraRemover:
	default:
		return nil, fmt.Errorf("unsupported type: %T", thing)
	}

	return &JiraDir{
		thing:        thing,
		client:       jc,
		SyntheticDir: trees.NewSyntheticDir(name, perm, user, group),
	}, nil
}

type CloseSaverHandle struct {
	onClose func() error
	trees.ReadWriteAtCloser
}

func (csh *CloseSaverHandle) Close() error {
	err := csh.ReadWriteAtCloser.Close()
	if err != nil {
		return err
	}

	if csh.onClose != nil {
		return csh.onClose()
	}

	return nil
}

// CloseSaver calls a callback on save if the file was opened for writing.
type CloseSaver struct {
	onClose    func() error
	forceTrunc bool
	trees.File
}

func (cs *CloseSaver) Open(user string, mode qp.OpenMode) (trees.ReadWriteAtCloser, error) {
	var closer func() error

	switch mode & 3 {
	case qp.OWRITE, qp.ORDWR:
		if cs.forceTrunc {
			mode |= qp.OTRUNC
		}
		closer = cs.onClose
	}

	hndl, err := cs.File.Open(user, mode)
	if err != nil {
		return nil, err
	}

	return &CloseSaverHandle{
		ReadWriteAtCloser: hndl,
		onClose:           closer,
	}, nil
}

func NewCloseSaver(file trees.File, onClose func() error) *CloseSaver {
	return &CloseSaver{
		onClose: onClose,
		File:    file,
	}
}

// CommandFile calls commands on write.
type CommandFile struct {
	cmds map[string]func([]string) error
	*trees.SyntheticFile
}

func (cf *CommandFile) Close() error { return nil }
func (cf *CommandFile) ReadAt(p []byte, offset int64) (int, error) {
	return 0, errors.New("cannot read from command file")
}

func (cf *CommandFile) WriteAt(p []byte, offset int64) (int, error) {
	args := strings.Split(strings.Trim(string(p), " \n"), " ")
	cmd := args[0]
	args = args[1:]

	if f, exists := cf.cmds[cmd]; exists {
		err := f(args)
		if err != nil {
			log.Printf("Command %s failed: %v", cmd, err)
		}
		return len(p), err
	}
	return len(p), errors.New("no such command")
}

func (cf *CommandFile) Open(user string, mode qp.OpenMode) (trees.ReadWriteAtCloser, error) {
	if !cf.CanOpen(user, mode) {
		return nil, trees.ErrPermissionDenied
	}

	return cf, nil
}

func NewCommandFile(name string, perms qp.FileMode, user, group string, cmds map[string]func([]string) error) *CommandFile {
	return &CommandFile{
		cmds:          cmds,
		SyntheticFile: trees.NewSyntheticFile(name, perms, user, group),
	}
}