file.go 6.7 KB

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