file.go 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298
  1. package repository
  2. import (
  3. "io"
  4. "os"
  5. "path"
  6. "path/filepath"
  7. "strings"
  8. "fyne.io/fyne/v2"
  9. "fyne.io/fyne/v2/storage"
  10. "fyne.io/fyne/v2/storage/repository"
  11. )
  12. // fileSchemePrefix is used for when we need a hard-coded version of "file://"
  13. // for string processing
  14. const fileSchemePrefix string = "file://"
  15. // declare conformance with repository types
  16. var _ repository.Repository = (*FileRepository)(nil)
  17. var _ repository.WritableRepository = (*FileRepository)(nil)
  18. var _ repository.HierarchicalRepository = (*FileRepository)(nil)
  19. var _ repository.ListableRepository = (*FileRepository)(nil)
  20. var _ repository.MovableRepository = (*FileRepository)(nil)
  21. var _ repository.CopyableRepository = (*FileRepository)(nil)
  22. var _ fyne.URIReadCloser = (*file)(nil)
  23. var _ fyne.URIWriteCloser = (*file)(nil)
  24. type file struct {
  25. *os.File
  26. uri fyne.URI
  27. }
  28. func (f *file) URI() fyne.URI {
  29. return f.uri
  30. }
  31. // FileRepository implements a simple wrapper around golang's filesystem
  32. // interface libraries. It should be registered by the driver on platforms
  33. // where it is appropriate to do so.
  34. //
  35. // This repository is suitable to handle the file:// scheme.
  36. //
  37. // Since: 2.0
  38. type FileRepository struct {
  39. }
  40. // NewFileRepository creates a new FileRepository instance.
  41. // The caller needs to call repository.Register() with the result of this function.
  42. //
  43. // Since: 2.0
  44. func NewFileRepository() *FileRepository {
  45. return &FileRepository{}
  46. }
  47. // Exists implements repository.Repository.Exists
  48. //
  49. // Since: 2.0
  50. func (r *FileRepository) Exists(u fyne.URI) (bool, error) {
  51. p := u.Path()
  52. _, err := os.Stat(p)
  53. ok := false
  54. if err == nil {
  55. ok = true
  56. } else if os.IsNotExist(err) {
  57. err = nil
  58. }
  59. return ok, err
  60. }
  61. func openFile(uri fyne.URI, create bool) (*file, error) {
  62. path := uri.Path()
  63. var f *os.File
  64. var err error
  65. if create {
  66. f, err = os.Create(path) // If it exists this will truncate which is what we wanted
  67. } else {
  68. f, err = os.Open(path)
  69. }
  70. return &file{File: f, uri: uri}, err
  71. }
  72. // Reader implements repository.Repository.Reader
  73. //
  74. // Since: 2.0
  75. func (r *FileRepository) Reader(u fyne.URI) (fyne.URIReadCloser, error) {
  76. return openFile(u, false)
  77. }
  78. // CanRead implements repository.Repository.CanRead
  79. //
  80. // Since: 2.0
  81. func (r *FileRepository) CanRead(u fyne.URI) (bool, error) {
  82. f, err := os.OpenFile(u.Path(), os.O_RDONLY, 0666)
  83. if err == nil {
  84. f.Close()
  85. } else {
  86. if os.IsPermission(err) {
  87. return false, nil
  88. }
  89. if os.IsNotExist(err) {
  90. return false, nil
  91. }
  92. return false, err
  93. }
  94. return true, nil
  95. }
  96. // Destroy implements repository.Repository.Destroy
  97. func (r *FileRepository) Destroy(scheme string) {
  98. // do nothing
  99. }
  100. // Writer implements repository.WritableRepository.Writer
  101. //
  102. // Since: 2.0
  103. func (r *FileRepository) Writer(u fyne.URI) (fyne.URIWriteCloser, error) {
  104. return openFile(u, true)
  105. }
  106. // CanWrite implements repository.WritableRepository.CanWrite
  107. //
  108. // Since: 2.0
  109. func (r *FileRepository) CanWrite(u fyne.URI) (bool, error) {
  110. f, err := os.OpenFile(u.Path(), os.O_WRONLY, 0666)
  111. if err == nil {
  112. f.Close()
  113. } else {
  114. if os.IsPermission(err) {
  115. return false, nil
  116. }
  117. if os.IsNotExist(err) {
  118. // We may need to do extra logic to check if the
  119. // directory is writable, but presumably the
  120. // IsPermission check covers this.
  121. return true, nil
  122. }
  123. return false, err
  124. }
  125. return true, nil
  126. }
  127. // Delete implements repository.WritableRepository.Delete
  128. //
  129. // Since: 2.0
  130. func (r *FileRepository) Delete(u fyne.URI) error {
  131. return os.Remove(u.Path())
  132. }
  133. // Parent implements repository.HierarchicalRepository.Parent
  134. //
  135. // Since: 2.0
  136. func (r *FileRepository) Parent(u fyne.URI) (fyne.URI, error) {
  137. s := u.String()
  138. // trim trailing slash
  139. s = strings.TrimSuffix(s, "/")
  140. // trim the scheme
  141. s = strings.TrimPrefix(s, fileSchemePrefix)
  142. // Completely empty URI with just a scheme
  143. if s == "" {
  144. return nil, repository.ErrURIRoot
  145. }
  146. parent := ""
  147. // use the system native path resolution
  148. parent = filepath.Dir(s)
  149. if parent[len(parent)-1] != filepath.Separator {
  150. parent += "/"
  151. }
  152. // only root is it's own parent
  153. if filepath.Clean(parent) == filepath.Clean(s) {
  154. return nil, repository.ErrURIRoot
  155. }
  156. return storage.NewFileURI(parent), nil
  157. }
  158. // Child implements repository.HierarchicalRepository.Child
  159. //
  160. // Since: 2.0
  161. func (r *FileRepository) Child(u fyne.URI, component string) (fyne.URI, error) {
  162. newURI := u.Scheme() + "://" + u.Authority()
  163. newURI += path.Join(u.Path(), component)
  164. // stick the query and fragment back on the end
  165. if query := u.Query(); len(query) > 0 {
  166. newURI += "?" + query
  167. }
  168. if fragment := u.Fragment(); len(fragment) > 0 {
  169. newURI += "#" + fragment
  170. }
  171. return storage.ParseURI(newURI)
  172. }
  173. // List implements repository.ListableRepository.List()
  174. //
  175. // Since: 2.0
  176. func (r *FileRepository) List(u fyne.URI) ([]fyne.URI, error) {
  177. path := u.Path()
  178. files, err := os.ReadDir(path)
  179. if err != nil {
  180. return nil, err
  181. }
  182. urilist := []fyne.URI{}
  183. for _, f := range files {
  184. uri := storage.NewFileURI(filepath.Join(path, f.Name()))
  185. urilist = append(urilist, uri)
  186. }
  187. return urilist, nil
  188. }
  189. // CreateListable implements repository.ListableRepository.CreateListable.
  190. func (r *FileRepository) CreateListable(u fyne.URI) error {
  191. path := u.Path()
  192. err := os.Mkdir(path, 0755)
  193. return err
  194. }
  195. // CanList implements repository.ListableRepository.CanList()
  196. //
  197. // Since: 2.0
  198. func (r *FileRepository) CanList(u fyne.URI) (bool, error) {
  199. p := u.Path()
  200. info, err := os.Stat(p)
  201. if err != nil {
  202. if os.IsNotExist(err) {
  203. return false, nil
  204. }
  205. return false, err
  206. }
  207. if !info.IsDir() {
  208. return false, nil
  209. }
  210. // We know it is a directory, but we don't know if we can read it, so
  211. // we'll just try to do so and see if we get a permissions error.
  212. f, err := os.Open(p)
  213. if err == nil {
  214. _, err = f.Readdir(1)
  215. f.Close()
  216. }
  217. if err != nil && err != io.EOF {
  218. return false, err
  219. }
  220. if os.IsPermission(err) {
  221. return false, nil
  222. }
  223. // it is a directory, and checking the permissions did not error out
  224. return true, nil
  225. }
  226. // Copy implements repository.CopyableRepository.Copy()
  227. //
  228. // Since: 2.0
  229. func (r *FileRepository) Copy(source, destination fyne.URI) error {
  230. // NOTE: as far as I can tell, golang does not have an optimized Copy
  231. // function - everything I can find on the 'net suggests doing more
  232. // or less the equivalent of GenericCopy(), hence why that is used.
  233. return repository.GenericCopy(source, destination)
  234. }
  235. // Move implements repository.MovableRepository.Move()
  236. //
  237. // Since: 2.0
  238. func (r *FileRepository) Move(source, destination fyne.URI) error {
  239. // NOTE: as far as I can tell, golang does not have an optimized Move
  240. // function - everything I can find on the 'net suggests doing more
  241. // or less the equivalent of GenericMove(), hence why that is used.
  242. return repository.GenericMove(source, destination)
  243. }