memory.go 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331
  1. package repository
  2. import (
  3. "fmt"
  4. "io"
  5. "strings"
  6. "fyne.io/fyne/v2"
  7. "fyne.io/fyne/v2/storage"
  8. "fyne.io/fyne/v2/storage/repository"
  9. )
  10. // declare conformance to interfaces
  11. var _ io.ReadCloser = (*nodeReaderWriter)(nil)
  12. var _ io.WriteCloser = (*nodeReaderWriter)(nil)
  13. var _ fyne.URIReadCloser = (*nodeReaderWriter)(nil)
  14. var _ fyne.URIWriteCloser = (*nodeReaderWriter)(nil)
  15. // declare conformance with repository types
  16. var _ repository.Repository = (*InMemoryRepository)(nil)
  17. var _ repository.WritableRepository = (*InMemoryRepository)(nil)
  18. var _ repository.HierarchicalRepository = (*InMemoryRepository)(nil)
  19. var _ repository.CopyableRepository = (*InMemoryRepository)(nil)
  20. var _ repository.MovableRepository = (*InMemoryRepository)(nil)
  21. var _ repository.ListableRepository = (*InMemoryRepository)(nil)
  22. // nodeReaderWriter allows reading or writing to elements in a InMemoryRepository
  23. type nodeReaderWriter struct {
  24. path string
  25. repo *InMemoryRepository
  26. writing bool
  27. readCursor int
  28. writeCursor int
  29. }
  30. // InMemoryRepository implements an in-memory version of the
  31. // repository.Repository type. It is useful for writing test cases, and may
  32. // also be of use as a template for people wanting to implement their own
  33. // "virtual repository". In future, we may consider moving this into the public
  34. // API.
  35. //
  36. // Because of it's design, this repository has several quirks:
  37. //
  38. // * The Parent() of a path that exists does not necessarily exist
  39. //
  40. // - Listing takes O(number of extant paths in the repository), rather than
  41. // O(number of children of path being listed).
  42. //
  43. // This repository is not designed to be particularly fast or robust, but
  44. // rather to be simple and easy to read. If you need performance, look
  45. // elsewhere.
  46. //
  47. // Since: 2.0
  48. type InMemoryRepository struct {
  49. // Data is exposed to allow tests to directly insert their own data
  50. // without having to go through the API
  51. Data map[string][]byte
  52. scheme string
  53. }
  54. // Read implements io.Reader.Read
  55. func (n *nodeReaderWriter) Read(p []byte) (int, error) {
  56. // first make sure the requested path actually exists
  57. data, ok := n.repo.Data[n.path]
  58. if !ok {
  59. return 0, fmt.Errorf("path '%s' not present in InMemoryRepository", n.path)
  60. }
  61. // copy it into p - we maintain counts since len(data) may be smaller
  62. // than len(p)
  63. count := 0
  64. j := 0 // index into p
  65. for ; (j < len(p)) && (n.readCursor < len(data)); n.readCursor++ {
  66. p[j] = data[n.readCursor]
  67. count++
  68. j++
  69. }
  70. // generate EOF if needed
  71. var err error = nil
  72. if n.readCursor >= len(data) {
  73. err = io.EOF
  74. }
  75. return count, err
  76. }
  77. // Close implements io.Closer.Close
  78. func (n *nodeReaderWriter) Close() error {
  79. n.readCursor = 0
  80. n.writeCursor = 0
  81. n.writing = false
  82. return nil
  83. }
  84. // Write implements io.Writer.Write
  85. //
  86. // This implementation automatically creates the path n.path if it does not
  87. // exist. If it does exist, it is overwritten.
  88. func (n *nodeReaderWriter) Write(p []byte) (int, error) {
  89. // guarantee that the path exists
  90. _, ok := n.repo.Data[n.path]
  91. if !ok {
  92. n.repo.Data[n.path] = []byte{}
  93. }
  94. // overwrite the file if we haven't already started writing to it
  95. if !n.writing {
  96. n.repo.Data[n.path] = make([]byte, 0)
  97. n.writing = true
  98. }
  99. // copy the data into the node buffer
  100. count := 0
  101. start := n.writeCursor
  102. for ; n.writeCursor < start+len(p); n.writeCursor++ {
  103. // extend the file if needed
  104. if len(n.repo.Data) < n.writeCursor+len(p) {
  105. n.repo.Data[n.path] = append(n.repo.Data[n.path], 0)
  106. }
  107. n.repo.Data[n.path][n.writeCursor] = p[n.writeCursor-start]
  108. count++
  109. }
  110. return count, nil
  111. }
  112. // Name implements fyne.URIReadCloser.URI and fyne.URIWriteCloser.URI
  113. func (n *nodeReaderWriter) URI() fyne.URI {
  114. // discarding the error because this should never fail
  115. u, _ := storage.ParseURI(n.repo.scheme + "://" + n.path)
  116. return u
  117. }
  118. // NewInMemoryRepository creates a new InMemoryRepository instance. It must be
  119. // given the scheme it is registered for. The caller needs to call
  120. // repository.Register() on the result of this function.
  121. //
  122. // Since: 2.0
  123. func NewInMemoryRepository(scheme string) *InMemoryRepository {
  124. return &InMemoryRepository{
  125. Data: make(map[string][]byte),
  126. scheme: scheme,
  127. }
  128. }
  129. // Exists implements repository.Repository.Exists
  130. //
  131. // Since: 2.0
  132. func (m *InMemoryRepository) Exists(u fyne.URI) (bool, error) {
  133. path := u.Path()
  134. if path == "" {
  135. return false, fmt.Errorf("invalid path '%s'", path)
  136. }
  137. _, ok := m.Data[path]
  138. return ok, nil
  139. }
  140. // Reader implements repository.Repository.Reader
  141. //
  142. // Since: 2.0
  143. func (m *InMemoryRepository) Reader(u fyne.URI) (fyne.URIReadCloser, error) {
  144. path := u.Path()
  145. if path == "" {
  146. return nil, fmt.Errorf("invalid path '%s'", path)
  147. }
  148. _, ok := m.Data[path]
  149. if !ok {
  150. return nil, fmt.Errorf("no such path '%s' in InMemoryRepository", path)
  151. }
  152. return &nodeReaderWriter{path: path, repo: m}, nil
  153. }
  154. // CanRead implements repository.Repository.CanRead
  155. //
  156. // Since: 2.0
  157. func (m *InMemoryRepository) CanRead(u fyne.URI) (bool, error) {
  158. path := u.Path()
  159. if path == "" {
  160. return false, fmt.Errorf("invalid path '%s'", path)
  161. }
  162. _, ok := m.Data[path]
  163. return ok, nil
  164. }
  165. // Destroy implements repository.Repository.Destroy
  166. func (m *InMemoryRepository) Destroy(scheme string) {
  167. // do nothing
  168. }
  169. // Writer implements repository.WritableRepository.Writer
  170. //
  171. // Since: 2.0
  172. func (m *InMemoryRepository) Writer(u fyne.URI) (fyne.URIWriteCloser, error) {
  173. path := u.Path()
  174. if path == "" {
  175. return nil, fmt.Errorf("invalid path '%s'", path)
  176. }
  177. return &nodeReaderWriter{path: path, repo: m}, nil
  178. }
  179. // CanWrite implements repository.WritableRepository.CanWrite
  180. //
  181. // Since: 2.0
  182. func (m *InMemoryRepository) CanWrite(u fyne.URI) (bool, error) {
  183. if p := u.Path(); p == "" {
  184. return false, fmt.Errorf("invalid path '%s'", p)
  185. }
  186. return true, nil
  187. }
  188. // Delete implements repository.WritableRepository.Delete
  189. //
  190. // Since: 2.0
  191. func (m *InMemoryRepository) Delete(u fyne.URI) error {
  192. path := u.Path()
  193. _, ok := m.Data[path]
  194. if ok {
  195. delete(m.Data, path)
  196. }
  197. return nil
  198. }
  199. // Parent implements repository.HierarchicalRepository.Parent
  200. //
  201. // Since: 2.0
  202. func (m *InMemoryRepository) Parent(u fyne.URI) (fyne.URI, error) {
  203. return repository.GenericParent(u)
  204. }
  205. // Child implements repository.HierarchicalRepository.Child
  206. //
  207. // Since: 2.0
  208. func (m *InMemoryRepository) Child(u fyne.URI, component string) (fyne.URI, error) {
  209. return repository.GenericChild(u, component)
  210. }
  211. // Copy implements repository.CopyableRepository.Copy()
  212. //
  213. // Since: 2.0
  214. func (m *InMemoryRepository) Copy(source, destination fyne.URI) error {
  215. return repository.GenericCopy(source, destination)
  216. }
  217. // Move implements repository.MovableRepository.Move()
  218. //
  219. // Since: 2.0
  220. func (m *InMemoryRepository) Move(source, destination fyne.URI) error {
  221. return repository.GenericMove(source, destination)
  222. }
  223. // CanList implements repository.ListableRepository.CanList()
  224. //
  225. // Since: 2.0
  226. func (m *InMemoryRepository) CanList(u fyne.URI) (bool, error) {
  227. return m.Exists(u)
  228. }
  229. // List implements repository.ListableRepository.List()
  230. //
  231. // Since: 2.0
  232. func (m *InMemoryRepository) List(u fyne.URI) ([]fyne.URI, error) {
  233. // Get the prefix, and make sure it ends with a path separator so that
  234. // HasPrefix() will only find things that are children of it - this
  235. // solves the edge case where you have say '/foo/bar' and
  236. // '/foo/barbaz'.
  237. prefix := u.Path()
  238. if len(prefix) > 0 && prefix[len(prefix)-1] != '/' {
  239. prefix = prefix + "/"
  240. }
  241. prefixSplit := strings.Split(prefix, "/")
  242. prefixSplitLen := len(prefixSplit)
  243. // Now we can simply loop over all the paths and find the ones with an
  244. // appropriate prefix, then eliminate those with too many path
  245. // components.
  246. listing := []fyne.URI{}
  247. for p := range m.Data {
  248. // We are going to compare ncomp with the number of elements in
  249. // prefixSplit, which is guaranteed to have a trailing slash,
  250. // so we want to also make pSplit be counted in ncomp like it
  251. // does not have one.
  252. pSplit := strings.Split(p, "/")
  253. ncomp := len(pSplit)
  254. if len(p) > 0 && p[len(p)-1] == '/' {
  255. ncomp--
  256. }
  257. if strings.HasPrefix(p, prefix) && ncomp == prefixSplitLen {
  258. uri, err := storage.ParseURI(m.scheme + "://" + p)
  259. if err != nil {
  260. return nil, err
  261. }
  262. listing = append(listing, uri)
  263. }
  264. }
  265. return listing, nil
  266. }
  267. // CreateListable impelements repository.ListableRepository.CreateListable.
  268. //
  269. // Since: 2.0
  270. func (m *InMemoryRepository) CreateListable(u fyne.URI) error {
  271. ex, err := m.Exists(u)
  272. if err != nil {
  273. return err
  274. }
  275. path := u.Path()
  276. if ex {
  277. return fmt.Errorf("cannot create '%s' as a listable path because it already exists", path)
  278. }
  279. m.Data[path] = []byte{}
  280. return nil
  281. }