Compare commits

...

3 Commits

5 changed files with 199 additions and 20 deletions

View File

@ -19,7 +19,7 @@ type Backend interface {
// NewTexture creates a new texture from an image. The backend must
// reject any texture that was not made by it.
NewTexture (image.Image) canvas.Texture
NewTexture (image.Image) canvas.TextureCloser
// Run runs the event loop until Stop() is called, or the backend
// experiences a fatal error.

View File

@ -5,28 +5,16 @@ import "image"
// Texture is a handle that points to a 2D raster image managed by the backend.
type Texture interface {
io.Closer
image.Image
// Clip returns a smaller section of this texture, pointing to the same
// internal data. Becaue of this, closing a clipped section will close
// the original texture as well.
// internal data.
Clip (image.Rectangle) Texture
// Bounds returns the size of this texture.
Bounds () image.Rectangle
}
type protectedTexture struct {
// TextureCloser is a texture that can be closed. Anything that receives a
// TextureCloser must close it after use.
type TextureCloser interface {
Texture
}
func (protectedTexture) Close () error {
return nil
}
// Protect makes the Close() method of a texture do nothing. This is useful if
// several of the same texture are given out to different objects, but only one
// has the responsibility of closing it.
func Protect (texture Texture) Texture {
return protectedTexture { Texture: texture }
io.Closer
}

187
path.go Normal file
View File

@ -0,0 +1,187 @@
package tomo
import "io"
import "os"
import "io/fs"
import "strings"
import "path/filepath"
// FS is Tomo's implementation of fs.FS. It provides access to a specific part
// of the filesystem.
type FS struct {
path string
}
// FileWriter is a writable version of fs.File.
type FileWriter interface {
interface { fs.File; io.Writer }
}
// ApplicationUserDataFS returns an FS that an application can use to store user
// data files.
func ApplicationUserDataFS (app ApplicationDescription) (*FS, error) {
return appFs(userDataDir, app)
}
// ApplicationUserConfigFS returns an FS that an application can use to store
// user configuration files.
func ApplicationUserConfigFS (app ApplicationDescription) (*FS, error) {
configDir, err := os.UserConfigDir()
if err != nil { return nil, err }
return appFs(configDir, app)
}
// ApplicationUserCacheFS returns an FS that an application can use to store
// user cache files.
func ApplicationUserCacheFS (app ApplicationDescription) (*FS, error) {
cacheDir, err := os.UserCacheDir()
if err != nil { return nil, err }
return appFs(cacheDir, app)
}
func pathErr (op, path string, err error) error {
return &fs.PathError {
Op: op,
Path: path,
Err: err,
}
}
func appFs (root string, app ApplicationDescription) (*FS, error) {
// remove slashes
appname := app.String()
appname = strings.ReplaceAll(appname, "/", "-")
appname = strings.ReplaceAll(appname, "\\", "-")
path := filepath.Join(root, appname)
// ensure the directory actually exists
err := os.MkdirAll(path, 755)
if err != nil { return nil, err }
return &FS { path: path }, nil
}
func (this FS) subPath (name string) (string, error) {
if !fs.ValidPath(name) { return "", fs.ErrInvalid }
if strings.Contains(name, "/") { return "", fs.ErrInvalid }
return filepath.Join(this.path, name), nil
}
// Open opens the named file.
func (this FS) Open (name string) (fs.File, error) {
path, err := this.subPath(name)
if err != nil {
return nil, pathErr("open", name, err)
}
return os.Open(path)
}
// Create creates or truncates the named file.
func (this FS) Create (name string) (FileWriter, error) {
path, err := this.subPath(name)
if err != nil {
return nil, pathErr("create", name, err)
}
return os.Create(path)
}
// OpenFile is the generalized open call; most users will use Open or Create
// instead.
func (this FS) OpenFile (
name string,
flag int,
perm os.FileMode,
) (
FileWriter,
error,
) {
path, err := this.subPath(name)
if err != nil {
return nil, pathErr("open", name, err)
}
return os.OpenFile(path, flag, perm)
}
// ReadDir reads the named directory and returns a list of directory entries
// sorted by filename.
func (this FS) ReadDir (name string) ([]fs.DirEntry, error) {
path, err := this.subPath(name)
if err != nil {
return nil, pathErr("readdir", name, err)
}
return os.ReadDir(path)
}
// ReadFile reads the named file and returns its contents.
// A successful call returns a nil error, not io.EOF.
// (Because ReadFile reads the whole file, the expected EOF
// from the final Read is not treated as an error to be reported.)
//
// The caller is permitted to modify the returned byte slice.
func (this FS) ReadFile (name string) ([]byte, error) {
path, err := this.subPath(name)
if err != nil {
return nil, pathErr("readfile", name, err)
}
return os.ReadFile(path)
}
// WriteFile writes data to the named file, creating it if necessary.
func (this FS) WriteFile (name string, data []byte, perm os.FileMode) error {
path, err := this.subPath(name)
if err != nil {
return pathErr("writefile", name, err)
}
return os.WriteFile(path, data, perm)
}
// Stat returns a FileInfo describing the file.
func (this FS) Stat (name string) (fs.FileInfo, error) {
path, err := this.subPath(name)
if err != nil {
return nil, pathErr("stat", name, err)
}
return os.Stat(path)
}
// Remove removes the named file or (empty) directory.
func (this FS) Remove (name string) error {
path, err := this.subPath(name)
if err != nil {
return pathErr("remove", name, err)
}
return os.Remove(path)
}
// RemoveAll removes name and any children it contains.
func (this FS) RemoveAll (name string) error {
path, err := this.subPath(name)
if err != nil {
return pathErr("removeall", name, err)
}
return os.RemoveAll(path)
}
// Rename renames (moves) oldname to newname.
func (this FS) Rename (oldname, newname string) error {
oldpath, err := this.subPath(oldname)
if err != nil {
return pathErr("rename", oldname, err)
}
newpath, err := this.subPath(newname)
if err != nil {
return pathErr("rename", newname, err)
}
return os.Rename(oldpath, newpath)
}

View File

@ -83,7 +83,7 @@ func NewContainerBox () ContainerBox {
// NewTexture creates a new texture from an image. When no longer in use, it
// must be freed using Close().
func NewTexture (source image.Image) canvas.Texture {
func NewTexture (source image.Image) canvas.TextureCloser {
assertBackend()
return backend.NewTexture(source)
}

View File

@ -6,6 +6,8 @@ import "os"
import "strings"
import "path/filepath"
var userDataDir string
func init () {
pathVariable := os.Getenv("TOMO_PLUGIN_PATH")
pluginPaths = strings.Split(pathVariable, ":")
@ -19,4 +21,6 @@ func init () {
pluginPaths,
filepath.Join(homeDir, ".local/lib/tomo/plugins"))
}
userDataDir = filepath.Join(homeDir, ".local/share")
}