Compare commits
3 Commits
57e6a9ff21
...
f9a85fd949
Author | SHA1 | Date |
---|---|---|
Sasha Koshka | f9a85fd949 | |
Sasha Koshka | 6ac653adb6 | |
Sasha Koshka | 7b28419432 |
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
2
tomo.go
2
tomo.go
|
@ -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)
|
||||
}
|
||||
|
|
4
unix.go
4
unix.go
|
@ -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")
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue