299 lines
6.7 KiB
Go
299 lines
6.7 KiB
Go
package repository
|
|
|
|
import (
|
|
"io"
|
|
"os"
|
|
"path"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"fyne.io/fyne/v2"
|
|
"fyne.io/fyne/v2/storage"
|
|
"fyne.io/fyne/v2/storage/repository"
|
|
)
|
|
|
|
// fileSchemePrefix is used for when we need a hard-coded version of "file://"
|
|
// for string processing
|
|
const fileSchemePrefix string = "file://"
|
|
|
|
// declare conformance with repository types
|
|
var _ repository.Repository = (*FileRepository)(nil)
|
|
var _ repository.WritableRepository = (*FileRepository)(nil)
|
|
var _ repository.HierarchicalRepository = (*FileRepository)(nil)
|
|
var _ repository.ListableRepository = (*FileRepository)(nil)
|
|
var _ repository.MovableRepository = (*FileRepository)(nil)
|
|
var _ repository.CopyableRepository = (*FileRepository)(nil)
|
|
|
|
var _ fyne.URIReadCloser = (*file)(nil)
|
|
var _ fyne.URIWriteCloser = (*file)(nil)
|
|
|
|
type file struct {
|
|
*os.File
|
|
uri fyne.URI
|
|
}
|
|
|
|
func (f *file) URI() fyne.URI {
|
|
return f.uri
|
|
}
|
|
|
|
// FileRepository implements a simple wrapper around golang's filesystem
|
|
// interface libraries. It should be registered by the driver on platforms
|
|
// where it is appropriate to do so.
|
|
//
|
|
// This repository is suitable to handle the file:// scheme.
|
|
//
|
|
// Since: 2.0
|
|
type FileRepository struct {
|
|
}
|
|
|
|
// NewFileRepository creates a new FileRepository instance.
|
|
// The caller needs to call repository.Register() with the result of this function.
|
|
//
|
|
// Since: 2.0
|
|
func NewFileRepository() *FileRepository {
|
|
return &FileRepository{}
|
|
}
|
|
|
|
// Exists implements repository.Repository.Exists
|
|
//
|
|
// Since: 2.0
|
|
func (r *FileRepository) Exists(u fyne.URI) (bool, error) {
|
|
p := u.Path()
|
|
|
|
_, err := os.Stat(p)
|
|
ok := false
|
|
|
|
if err == nil {
|
|
ok = true
|
|
} else if os.IsNotExist(err) {
|
|
err = nil
|
|
}
|
|
|
|
return ok, err
|
|
}
|
|
|
|
func openFile(uri fyne.URI, create bool) (*file, error) {
|
|
path := uri.Path()
|
|
var f *os.File
|
|
var err error
|
|
if create {
|
|
f, err = os.Create(path) // If it exists this will truncate which is what we wanted
|
|
} else {
|
|
f, err = os.Open(path)
|
|
}
|
|
return &file{File: f, uri: uri}, err
|
|
}
|
|
|
|
// Reader implements repository.Repository.Reader
|
|
//
|
|
// Since: 2.0
|
|
func (r *FileRepository) Reader(u fyne.URI) (fyne.URIReadCloser, error) {
|
|
return openFile(u, false)
|
|
}
|
|
|
|
// CanRead implements repository.Repository.CanRead
|
|
//
|
|
// Since: 2.0
|
|
func (r *FileRepository) CanRead(u fyne.URI) (bool, error) {
|
|
f, err := os.OpenFile(u.Path(), os.O_RDONLY, 0666)
|
|
if err == nil {
|
|
f.Close()
|
|
} else {
|
|
|
|
if os.IsPermission(err) {
|
|
return false, nil
|
|
}
|
|
|
|
if os.IsNotExist(err) {
|
|
return false, nil
|
|
}
|
|
|
|
return false, err
|
|
}
|
|
|
|
return true, nil
|
|
}
|
|
|
|
// Destroy implements repository.Repository.Destroy
|
|
func (r *FileRepository) Destroy(scheme string) {
|
|
// do nothing
|
|
}
|
|
|
|
// Writer implements repository.WritableRepository.Writer
|
|
//
|
|
// Since: 2.0
|
|
func (r *FileRepository) Writer(u fyne.URI) (fyne.URIWriteCloser, error) {
|
|
return openFile(u, true)
|
|
}
|
|
|
|
// CanWrite implements repository.WritableRepository.CanWrite
|
|
//
|
|
// Since: 2.0
|
|
func (r *FileRepository) CanWrite(u fyne.URI) (bool, error) {
|
|
f, err := os.OpenFile(u.Path(), os.O_WRONLY, 0666)
|
|
if err == nil {
|
|
f.Close()
|
|
} else {
|
|
|
|
if os.IsPermission(err) {
|
|
return false, nil
|
|
}
|
|
|
|
if os.IsNotExist(err) {
|
|
// We may need to do extra logic to check if the
|
|
// directory is writable, but presumably the
|
|
// IsPermission check covers this.
|
|
return true, nil
|
|
}
|
|
|
|
return false, err
|
|
}
|
|
|
|
return true, nil
|
|
}
|
|
|
|
// Delete implements repository.WritableRepository.Delete
|
|
//
|
|
// Since: 2.0
|
|
func (r *FileRepository) Delete(u fyne.URI) error {
|
|
return os.Remove(u.Path())
|
|
}
|
|
|
|
// Parent implements repository.HierarchicalRepository.Parent
|
|
//
|
|
// Since: 2.0
|
|
func (r *FileRepository) Parent(u fyne.URI) (fyne.URI, error) {
|
|
s := u.String()
|
|
|
|
// trim trailing slash
|
|
s = strings.TrimSuffix(s, "/")
|
|
|
|
// trim the scheme
|
|
s = strings.TrimPrefix(s, fileSchemePrefix)
|
|
|
|
// Completely empty URI with just a scheme
|
|
if s == "" {
|
|
return nil, repository.ErrURIRoot
|
|
}
|
|
|
|
parent := ""
|
|
// use the system native path resolution
|
|
parent = filepath.Dir(s)
|
|
if parent[len(parent)-1] != filepath.Separator {
|
|
parent += "/"
|
|
}
|
|
|
|
// only root is it's own parent
|
|
if filepath.Clean(parent) == filepath.Clean(s) {
|
|
return nil, repository.ErrURIRoot
|
|
}
|
|
|
|
return storage.NewFileURI(parent), nil
|
|
}
|
|
|
|
// Child implements repository.HierarchicalRepository.Child
|
|
//
|
|
// Since: 2.0
|
|
func (r *FileRepository) Child(u fyne.URI, component string) (fyne.URI, error) {
|
|
newURI := u.Scheme() + "://" + u.Authority()
|
|
newURI += path.Join(u.Path(), component)
|
|
|
|
// stick the query and fragment back on the end
|
|
if query := u.Query(); len(query) > 0 {
|
|
newURI += "?" + query
|
|
}
|
|
if fragment := u.Fragment(); len(fragment) > 0 {
|
|
newURI += "#" + fragment
|
|
}
|
|
|
|
return storage.ParseURI(newURI)
|
|
}
|
|
|
|
// List implements repository.ListableRepository.List()
|
|
//
|
|
// Since: 2.0
|
|
func (r *FileRepository) List(u fyne.URI) ([]fyne.URI, error) {
|
|
|
|
path := u.Path()
|
|
files, err := os.ReadDir(path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
urilist := []fyne.URI{}
|
|
|
|
for _, f := range files {
|
|
uri := storage.NewFileURI(filepath.Join(path, f.Name()))
|
|
urilist = append(urilist, uri)
|
|
}
|
|
|
|
return urilist, nil
|
|
}
|
|
|
|
// CreateListable implements repository.ListableRepository.CreateListable.
|
|
func (r *FileRepository) CreateListable(u fyne.URI) error {
|
|
path := u.Path()
|
|
err := os.Mkdir(path, 0755)
|
|
return err
|
|
}
|
|
|
|
// CanList implements repository.ListableRepository.CanList()
|
|
//
|
|
// Since: 2.0
|
|
func (r *FileRepository) CanList(u fyne.URI) (bool, error) {
|
|
p := u.Path()
|
|
info, err := os.Stat(p)
|
|
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
return false, nil
|
|
}
|
|
return false, err
|
|
}
|
|
|
|
if !info.IsDir() {
|
|
return false, nil
|
|
}
|
|
|
|
// We know it is a directory, but we don't know if we can read it, so
|
|
// we'll just try to do so and see if we get a permissions error.
|
|
f, err := os.Open(p)
|
|
if err == nil {
|
|
_, err = f.Readdir(1)
|
|
f.Close()
|
|
}
|
|
|
|
if err != nil && err != io.EOF {
|
|
return false, err
|
|
}
|
|
|
|
if os.IsPermission(err) {
|
|
return false, nil
|
|
}
|
|
|
|
// it is a directory, and checking the permissions did not error out
|
|
return true, nil
|
|
}
|
|
|
|
// Copy implements repository.CopyableRepository.Copy()
|
|
//
|
|
// Since: 2.0
|
|
func (r *FileRepository) Copy(source, destination fyne.URI) error {
|
|
// NOTE: as far as I can tell, golang does not have an optimized Copy
|
|
// function - everything I can find on the 'net suggests doing more
|
|
// or less the equivalent of GenericCopy(), hence why that is used.
|
|
|
|
return repository.GenericCopy(source, destination)
|
|
}
|
|
|
|
// Move implements repository.MovableRepository.Move()
|
|
//
|
|
// Since: 2.0
|
|
func (r *FileRepository) Move(source, destination fyne.URI) error {
|
|
// NOTE: as far as I can tell, golang does not have an optimized Move
|
|
// function - everything I can find on the 'net suggests doing more
|
|
// or less the equivalent of GenericMove(), hence why that is used.
|
|
|
|
return repository.GenericMove(source, destination)
|
|
}
|