Files and URIs
Bellui names files and other resources with a
bellui.URI rather than
a path. A URI starts with a scheme, such as file:, and the scheme decides
which code handles it. That handler is called a repository. This is how a
file chosen on an Android phone (a content: URI) and a file on a desktop disk
(a file: URI) can go through the same functions.
The functions in the storage
package look up the repository for a URI and call it. Use them rather than the
os package when your code has to work on every platform.
Making URIs
dir := storage.NewFileURI(filepath.Join(os.TempDir(), "bellui-demo"))
file, err := storage.Child(dir, "hello.txt")
if err != nil {
return err
}
log.WriteString(file.String() + "\n") // file:///…/bellui-demo/hello.txt
log.WriteString(file.Name() + "\n") // hello.txt
log.WriteString(file.Extension() + "\n") // .txt
log.WriteString(file.MimeType() + "\n") // text/plainstorage.NewFileURIturns a file path into afile:URI. A relative path is made absolute first.storage.ParseURIparses a URI string.storage.Childandstorage.Parentmove down and up the hierarchy.Parentof a root returnsrepository.ErrURIRoot.
A URI also tells you its Name(), Extension() (with the dot), MimeType(),
Scheme() and Path(). Compare two URIs with
storage.EqualURI.
Writing and reading
storage.Writer
opens a resource for writing. For a file it creates the file, or empties it if
it already exists. Always check the error from Close: it is part of saving.
wr, err := storage.Writer(file)
if err != nil {
return err
}
if _, err := wr.Write([]byte("Hello from Bellui\n")); err != nil {
wr.Close()
return err
}
if err := wr.Close(); err != nil {
return err
}storage.Reader
opens it for reading.
r, err := storage.Reader(file)
if err != nil {
return err
}
defer r.Close()
data, err := io.ReadAll(r)
if err != nil {
return err
}
log.WriteString("read: " + string(data))storage.Appender
opens a resource to add to its end. CanRead and CanWrite tell you in
advance whether reading or writing is possible.
If you only need the bytes as a
bellui.Resource,
for example to show an image,
storage.LoadResourceFromURI
reads the whole resource at once.
Folders and listing
A resource that can contain others, such as a folder, is called listable.
storage.CreateListable
creates one. For files this is “make a folder”; it fails if the parent does
not exist and does not create parents for you.
if ok, err := storage.Exists(dir); err != nil {
return err
} else if !ok {
if err := storage.CreateListable(dir); err != nil {
return err
}
}storage.List
returns the URIs inside a listable resource. CanList tells you whether a URI
is listable, and
storage.ListerForURI
converts a URI into a
bellui.ListableURI,
the type the folder dialog returns.
children, err := storage.List(dir)
if err != nil {
return err
}
for _, child := range children {
log.WriteString("child: " + child.Name() + "\n")
}storage.IsHidden
applies the platform’s rule for hidden files: a leading dot on Unix-like
systems, the hidden attribute on Windows. It always reports false on mobile
and the web.
Copying, moving and deleting
backup, _ := storage.Child(dir, "hello.bak")
if err := storage.Copy(file, backup); err != nil {
return err
}
if err := storage.Delete(backup); err != nil {
return err
}storage.Copy,
storage.Move,
storage.Delete
and storage.DeleteAll
do what their names say. DeleteAll removes a folder and everything in it.
Which schemes work where
Not every repository supports every operation. When an operation is not
available for a URI’s scheme, the function returns
repository.ErrOperationNotSupported.
The schemes Bellui registers are:
| Scheme | Where | Notes |
|---|---|---|
file | Desktop, Android, iOS | Local files. Full read, write and list support on the desktop. |
content | Android | Files handed over by the Android document picker. |
http, https | Everywhere | Reading from the web. |
idbfile | Web | The browser’s IndexedDB, used for app storage on the web. |
To add your own scheme, see Custom repositories.
The complete example
// Reading, writing and listing files through URIs.
package main
import (
"io"
"os"
"path/filepath"
"strings"
"github.com/belluikit/bellui"
"github.com/belluikit/bellui/app"
"github.com/belluikit/bellui/container"
"github.com/belluikit/bellui/storage"
"github.com/belluikit/bellui/widget"
)
func main() {
a := app.New()
w := a.NewWindow("Files and URIs")
out := widget.NewLabel("")
run := widget.NewButton("Run", func() {
var log strings.Builder
if err := demo(&log); err != nil {
log.WriteString("error: " + err.Error() + "\n")
}
out.SetText(log.String())
})
w.SetContent(container.NewVBox(run, out))
bellui.ShowAndRun(w)
}
func demo(log *strings.Builder) error {
dir := storage.NewFileURI(filepath.Join(os.TempDir(), "bellui-demo"))
file, err := storage.Child(dir, "hello.txt")
if err != nil {
return err
}
log.WriteString(file.String() + "\n") // file:///…/bellui-demo/hello.txt
log.WriteString(file.Name() + "\n") // hello.txt
log.WriteString(file.Extension() + "\n") // .txt
log.WriteString(file.MimeType() + "\n") // text/plain
if ok, err := storage.Exists(dir); err != nil {
return err
} else if !ok {
if err := storage.CreateListable(dir); err != nil {
return err
}
}
wr, err := storage.Writer(file)
if err != nil {
return err
}
if _, err := wr.Write([]byte("Hello from Bellui\n")); err != nil {
wr.Close()
return err
}
if err := wr.Close(); err != nil {
return err
}
r, err := storage.Reader(file)
if err != nil {
return err
}
defer r.Close()
data, err := io.ReadAll(r)
if err != nil {
return err
}
log.WriteString("read: " + string(data))
children, err := storage.List(dir)
if err != nil {
return err
}
for _, child := range children {
log.WriteString("child: " + child.Name() + "\n")
}
backup, _ := storage.Child(dir, "hello.bak")
if err := storage.Copy(file, backup); err != nil {
return err
}
if err := storage.Delete(backup); err != nil {
return err
}
return nil
}