webdavfs.go 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  1. package webdavfs
  2. import (
  3. "errors"
  4. "io"
  5. "log"
  6. "os"
  7. "path/filepath"
  8. "regexp"
  9. "strings"
  10. "time"
  11. "github.com/studio-b12/gowebdav"
  12. )
  13. /*
  14. WebDAV Client
  15. This script is design as a wrapper of the studio-b12/gowebdav module
  16. that allow access to webdav network drive in ArozOS and allow arozos
  17. cross-mounting each others
  18. */
  19. type WebDAVFileSystem struct {
  20. UUID string
  21. Hierarchy string
  22. root string
  23. user string
  24. tmp string
  25. c *gowebdav.Client
  26. }
  27. type myFileType struct {
  28. io.Reader
  29. io.Closer
  30. }
  31. func NewWebDAVMount(UUID string, Hierarchy string, root string, user string, password string, tmpBuff string) (*WebDAVFileSystem, error) {
  32. //Connect to webdav server
  33. c := gowebdav.NewClient(root, user, password)
  34. err := c.Connect()
  35. if err != nil {
  36. log.Println("[WebDAV FS] Unable to connect to remote: ", err.Error())
  37. return nil, err
  38. } else {
  39. log.Println("[WebDAV FS] Connected to remote: " + root)
  40. }
  41. //Create tmp buff folder if not exists
  42. os.MkdirAll(tmpBuff, 0775)
  43. return &WebDAVFileSystem{
  44. UUID: UUID,
  45. Hierarchy: Hierarchy,
  46. c: c,
  47. root: root,
  48. user: user,
  49. tmp: tmpBuff,
  50. }, nil
  51. }
  52. func (e WebDAVFileSystem) Chmod(filename string, mode os.FileMode) error {
  53. return errors.New("filesystem type not supported")
  54. }
  55. func (e WebDAVFileSystem) Chown(filename string, uid int, gid int) error {
  56. return errors.New("filesystem type not supported")
  57. }
  58. func (e WebDAVFileSystem) Chtimes(filename string, atime time.Time, mtime time.Time) error {
  59. return errors.New("filesystem type not supported")
  60. }
  61. func (e WebDAVFileSystem) Create(filename string) (*os.File, error) {
  62. return nil, errors.New("filesystem type not supported")
  63. }
  64. func (e WebDAVFileSystem) Mkdir(filename string, mode os.FileMode) error {
  65. filename = filterFilepath(filepath.ToSlash(filepath.Clean(filename)))
  66. return e.c.Mkdir(filename, mode)
  67. }
  68. func (e WebDAVFileSystem) MkdirAll(filename string, mode os.FileMode) error {
  69. filename = filterFilepath(filepath.ToSlash(filepath.Clean(filename)))
  70. return e.c.MkdirAll(filename, mode)
  71. }
  72. func (e WebDAVFileSystem) Name() string {
  73. return ""
  74. }
  75. func (e WebDAVFileSystem) Open(filename string) (*os.File, error) {
  76. return nil, errors.New("filesystem type not supported")
  77. }
  78. func (e WebDAVFileSystem) OpenFile(filename string, flag int, perm os.FileMode) (*os.File, error) {
  79. //Buffer the target file to memory
  80. //To be implement: Wait for Golang's fs.File.Write function to be released
  81. //f := bufffs.New(filename)
  82. //return f, nil
  83. return nil, errors.New("filesystem type not supported")
  84. }
  85. func (e WebDAVFileSystem) Remove(filename string) error {
  86. filename = filterFilepath(filepath.ToSlash(filepath.Clean(filename)))
  87. return e.c.Remove(filename)
  88. }
  89. func (e WebDAVFileSystem) RemoveAll(filename string) error {
  90. filename = filterFilepath(filepath.ToSlash(filepath.Clean(filename)))
  91. return e.c.RemoveAll(filename)
  92. }
  93. func (e WebDAVFileSystem) Rename(oldname, newname string) error {
  94. oldname = filterFilepath(filepath.ToSlash(filepath.Clean(oldname)))
  95. newname = filterFilepath(filepath.ToSlash(filepath.Clean(newname)))
  96. return e.c.Rename(oldname, newname, true)
  97. }
  98. func (e WebDAVFileSystem) Stat(filename string) (os.FileInfo, error) {
  99. filename = filterFilepath(filepath.ToSlash(filepath.Clean(filename)))
  100. return e.c.Stat(filename)
  101. }
  102. func (e WebDAVFileSystem) VirtualPathToRealPath(subpath string, username string) (string, error) {
  103. subpath = filterFilepath(filepath.ToSlash(filepath.Clean(subpath)))
  104. if strings.HasPrefix(subpath, e.UUID+":") {
  105. //This is full virtual path. Trim the uuid and correct the subpath
  106. subpath = strings.TrimPrefix(subpath, e.UUID+":")
  107. }
  108. if e.Hierarchy == "user" {
  109. return filepath.ToSlash(filepath.Join("users", username, subpath)), nil
  110. } else if e.Hierarchy == "public" {
  111. return filepath.ToSlash(subpath), nil
  112. }
  113. return "", errors.New("unsupported filesystem hierarchy")
  114. }
  115. func (e WebDAVFileSystem) RealPathToVirtualPath(rpath string, username string) (string, error) {
  116. return e.UUID + ":" + filepath.ToSlash(rpath), nil
  117. }
  118. func (e WebDAVFileSystem) FileExists(filename string) bool {
  119. filename = filterFilepath(filepath.ToSlash(filepath.Clean(filename)))
  120. _, err := e.c.Stat(filename)
  121. if os.IsNotExist(err) || err != nil {
  122. return false
  123. }
  124. return true
  125. }
  126. func (e WebDAVFileSystem) IsDir(filename string) bool {
  127. filename = filterFilepath(filepath.ToSlash(filepath.Clean(filename)))
  128. s, err := e.c.Stat(filename)
  129. if err != nil {
  130. return false
  131. }
  132. return s.IsDir()
  133. }
  134. //Notes: This is not actual Glob function. This just emulate Glob using ReadDir with max depth 1 layer
  135. func (e WebDAVFileSystem) Glob(wildcard string) ([]string, error) {
  136. wildcard = filepath.ToSlash(filepath.Clean(wildcard))
  137. if !strings.HasPrefix(wildcard, "/") {
  138. //Handle case for listing root, "*"
  139. wildcard = "/" + wildcard
  140. }
  141. fileInfos, err := e.c.ReadDir(filterFilepath(filepath.ToSlash(filepath.Clean(filepath.Dir(wildcard)))))
  142. if err != nil {
  143. return []string{}, err
  144. }
  145. validFiles := []string{}
  146. matchingRule := wildCardToRegexp(wildcard)
  147. for _, fileInfo := range fileInfos {
  148. thisFullPath := filepath.ToSlash(filepath.Join(filepath.Dir(wildcard), fileInfo.Name()))
  149. match, _ := regexp.MatchString(matchingRule, thisFullPath)
  150. if match {
  151. validFiles = append(validFiles, thisFullPath)
  152. }
  153. }
  154. return validFiles, nil
  155. }
  156. func (e WebDAVFileSystem) GetFileSize(filename string) int64 {
  157. filename = filterFilepath(filepath.ToSlash(filepath.Clean(filename)))
  158. s, err := e.Stat(filename)
  159. if err != nil {
  160. log.Println(err)
  161. return 0
  162. }
  163. return s.Size()
  164. }
  165. func (e WebDAVFileSystem) GetModTime(filename string) (int64, error) {
  166. filename = filterFilepath(filepath.ToSlash(filepath.Clean(filename)))
  167. s, err := e.Stat(filename)
  168. if err != nil {
  169. return 0, err
  170. }
  171. return s.ModTime().Unix(), nil
  172. }
  173. func (e WebDAVFileSystem) WriteFile(filename string, content []byte, mode os.FileMode) error {
  174. filename = filterFilepath(filepath.ToSlash(filepath.Clean(filename)))
  175. return e.c.Write(filename, content, mode)
  176. }
  177. func (e WebDAVFileSystem) ReadFile(filename string) ([]byte, error) {
  178. filename = filterFilepath(filepath.ToSlash(filepath.Clean(filename)))
  179. bytes, err := e.c.Read(filename)
  180. if err != nil {
  181. return []byte(""), err
  182. }
  183. return bytes, nil
  184. }
  185. func (e WebDAVFileSystem) WriteStream(filename string, stream io.Reader, mode os.FileMode) error {
  186. filename = filterFilepath(filepath.ToSlash(filepath.Clean(filename)))
  187. return e.c.WriteStream(filename, stream, mode)
  188. }
  189. func (e WebDAVFileSystem) ReadStream(filename string) (io.ReadCloser, error) {
  190. filename = filterFilepath(filepath.ToSlash(filepath.Clean(filename)))
  191. return e.c.ReadStream(filename)
  192. }
  193. func (e WebDAVFileSystem) Walk(rootpath string, walkFn filepath.WalkFunc) error {
  194. rootpath = filepath.ToSlash(filepath.Clean(rootpath))
  195. rootStat, err := e.Stat(rootpath)
  196. err = walkFn(rootpath, rootStat, err)
  197. if err != nil {
  198. return err
  199. }
  200. return e.walk(rootpath, walkFn)
  201. }
  202. func (e WebDAVFileSystem) walk(thisPath string, walkFun filepath.WalkFunc) error {
  203. files, err := e.c.ReadDir(thisPath)
  204. if err != nil {
  205. return err
  206. }
  207. for _, file := range files {
  208. thisFileFullPath := filepath.ToSlash(filepath.Join(thisPath, file.Name()))
  209. if file.IsDir() {
  210. err = walkFun(thisFileFullPath, file, nil)
  211. if err != nil {
  212. return err
  213. }
  214. err = e.walk(thisFileFullPath, walkFun)
  215. if err != nil {
  216. return err
  217. }
  218. } else {
  219. err = walkFun(thisFileFullPath, file, nil)
  220. if err != nil {
  221. return err
  222. }
  223. }
  224. }
  225. return nil
  226. }
  227. func filterFilepath(rawpath string) string {
  228. rawpath = strings.TrimSpace(rawpath)
  229. if strings.HasPrefix(rawpath, "./") {
  230. return rawpath[1:]
  231. } else if rawpath == "." || rawpath == "" {
  232. return "/"
  233. }
  234. return rawpath
  235. }
  236. func wildCardToRegexp(pattern string) string {
  237. var result strings.Builder
  238. for i, literal := range strings.Split(pattern, "*") {
  239. // Replace * with .*
  240. if i > 0 {
  241. result.WriteString(".*")
  242. }
  243. // Quote any regular expression meta characters in the
  244. // literal text.
  245. result.WriteString(regexp.QuoteMeta(literal))
  246. }
  247. return result.String()
  248. }