static.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428
  1. package filesystem
  2. import (
  3. "crypto/md5"
  4. "crypto/sha256"
  5. "encoding/hex"
  6. "errors"
  7. "fmt"
  8. "io"
  9. "log"
  10. "mime"
  11. "os"
  12. "os/exec"
  13. "path/filepath"
  14. "runtime"
  15. "strconv"
  16. "strings"
  17. "net/url"
  18. mimetype "github.com/gabriel-vasile/mimetype"
  19. "imuslab.com/arozos/mod/filesystem/arozfs"
  20. "imuslab.com/arozos/mod/filesystem/shortcut"
  21. )
  22. //Structure definations
  23. type FileData struct {
  24. Filename string
  25. Filepath string
  26. Realpath string
  27. IsDir bool
  28. Filesize int64
  29. Displaysize string
  30. ModTime int64
  31. IsShared bool
  32. Shortcut *arozfs.ShortcutData //This will return nil or undefined if it is not a shortcut file
  33. }
  34. type TrashedFile struct {
  35. Filename string
  36. Filepath string
  37. FileExt string
  38. IsDir bool
  39. Filesize int64
  40. RemoveTimestamp int64
  41. RemoveDate string
  42. OriginalPath string
  43. OriginalFilename string
  44. }
  45. type FileProperties struct {
  46. VirtualPath string
  47. StoragePath string
  48. Basename string
  49. VirtualDirname string
  50. StorageDirname string
  51. Ext string
  52. MimeType string
  53. Filesize int64
  54. Permission string
  55. LastModTime string
  56. LastModUnix int64
  57. IsDirectory bool
  58. }
  59. /*
  60. HierarchySpecificConfig Template
  61. */
  62. type EmptyHierarchySpecificConfig struct {
  63. HierarchyType string
  64. }
  65. func (e EmptyHierarchySpecificConfig) ResolveVrootPath(string, string) (string, error) {
  66. return "", nil
  67. }
  68. func (e EmptyHierarchySpecificConfig) ResolveRealPath(string, string) (string, error) {
  69. return "", nil
  70. }
  71. var DefaultEmptyHierarchySpecificConfig = EmptyHierarchySpecificConfig{
  72. HierarchyType: "placeholder",
  73. }
  74. //Check if the two file system are identical.
  75. func MatchingFileSystem(fsa *FileSystemHandler, fsb *FileSystemHandler) bool {
  76. return fsa.Filesystem == fsb.Filesystem
  77. }
  78. //Get the ID part of a virtual path, return ID, subpath and error
  79. func GetIDFromVirtualPath(vpath string) (string, string, error) {
  80. if !strings.Contains(vpath, ":") {
  81. return "", "", errors.New("Path missing Virtual Device ID. Given: " + vpath)
  82. }
  83. //Clean up the virutal path
  84. vpath = filepath.ToSlash(filepath.Clean(vpath))
  85. tmp := strings.Split(vpath, ":")
  86. vdID := tmp[0]
  87. pathSlice := tmp[1:]
  88. path := strings.Join(pathSlice, ":")
  89. return vdID, path, nil
  90. }
  91. func GetFileDataFromPath(fsh *FileSystemHandler, vpath string, realpath string, sizeRounding int) FileData {
  92. fileSize := fsh.FileSystemAbstraction.GetFileSize(realpath)
  93. displaySize := GetFileDisplaySize(fileSize, sizeRounding)
  94. modtime, _ := fsh.FileSystemAbstraction.GetModTime(realpath)
  95. var shortcutInfo *arozfs.ShortcutData = nil
  96. if filepath.Ext(realpath) == ".shortcut" {
  97. shortcutContent, err := fsh.FileSystemAbstraction.ReadFile(realpath)
  98. if err != nil {
  99. shortcutInfo = nil
  100. } else {
  101. shortcutInfo, err = shortcut.ReadShortcut(shortcutContent)
  102. if err != nil {
  103. shortcutInfo = nil
  104. }
  105. }
  106. }
  107. return FileData{
  108. Filename: filepath.Base(realpath),
  109. Filepath: vpath,
  110. Realpath: filepath.ToSlash(realpath),
  111. IsDir: fsh.FileSystemAbstraction.IsDir(realpath),
  112. Filesize: fileSize,
  113. Displaysize: displaySize,
  114. ModTime: modtime,
  115. IsShared: false,
  116. Shortcut: shortcutInfo,
  117. }
  118. }
  119. func CheckMounted(mountpoint string) bool {
  120. if runtime.GOOS == "windows" {
  121. //Windows
  122. //Check if the given folder exists
  123. info, err := os.Stat(mountpoint)
  124. if os.IsNotExist(err) {
  125. return false
  126. }
  127. return info.IsDir()
  128. } else {
  129. //Linux
  130. cmd := exec.Command("mountpoint", mountpoint)
  131. out, err := cmd.CombinedOutput()
  132. if err != nil {
  133. return false
  134. }
  135. outstring := strings.TrimSpace(string(out))
  136. if strings.Contains(outstring, " is a mountpoint") {
  137. return true
  138. } else {
  139. return false
  140. }
  141. }
  142. }
  143. func MountDevice(mountpt string, mountdev string, filesystem string) error {
  144. //Check if running under sudo mode and in linux
  145. if runtime.GOOS == "linux" {
  146. //Try to mount the file system
  147. if mountdev == "" {
  148. return errors.New("Disk with automount enabled has no mountdev value: " + mountpt)
  149. }
  150. if mountpt == "" {
  151. return errors.New("Invalid storage.json. Mount point not given or not exists for " + mountdev)
  152. }
  153. //Check if device exists
  154. if !FileExists(mountdev) {
  155. //Device driver not exists.
  156. return errors.New("Device not exists: " + mountdev)
  157. }
  158. //Mount the device
  159. if CheckMounted(mountpt) {
  160. log.Println(mountpt + " already mounted.")
  161. } else {
  162. log.Println("Mounting " + mountdev + "(" + filesystem + ") to " + filepath.Clean(mountpt))
  163. cmd := exec.Command("mount", "-t", filesystem, mountdev, filepath.Clean(mountpt))
  164. cmd.Stdout = os.Stdout
  165. cmd.Stderr = os.Stderr
  166. cmd.Run()
  167. }
  168. //Check if the path exists
  169. if !FileExists(mountpt) {
  170. //Mounted but path still not found. Skip this device
  171. return errors.New("Unable to find " + mountpt)
  172. }
  173. } else {
  174. return errors.New("Unsupported platform")
  175. }
  176. return nil
  177. }
  178. func GetFileSize(filename string) int64 {
  179. fi, err := os.Stat(filename)
  180. if err != nil {
  181. return 0
  182. }
  183. // get the size
  184. return fi.Size()
  185. }
  186. func IsInsideHiddenFolder(path string) bool {
  187. thisPathInfo := filepath.ToSlash(filepath.Clean(path))
  188. pathData := strings.Split(thisPathInfo, "/")
  189. for _, thispd := range pathData {
  190. if len(thispd) > 0 && thispd[:1] == "." {
  191. //This path contain one of the folder is hidden
  192. return true
  193. }
  194. }
  195. return false
  196. }
  197. /*
  198. Wildcard Replacement Glob, design to hanle path with [ or ] inside.
  199. You can also pass in normal path for globing if you are not sure.
  200. */
  201. func WGlob(path string) ([]string, error) {
  202. files, err := filepath.Glob(path)
  203. if err != nil {
  204. return []string{}, err
  205. }
  206. if strings.Contains(path, "[") == true || strings.Contains(path, "]") == true {
  207. if len(files) == 0 {
  208. //Handle reverse check. Replace all [ and ] with ?
  209. newSearchPath := strings.ReplaceAll(path, "[", "?")
  210. newSearchPath = strings.ReplaceAll(newSearchPath, "]", "?")
  211. //Scan with all the similar structure except [ and ]
  212. tmpFilelist, _ := filepath.Glob(newSearchPath)
  213. for _, file := range tmpFilelist {
  214. file = filepath.ToSlash(file)
  215. if strings.Contains(file, filepath.ToSlash(filepath.Dir(path))) {
  216. files = append(files, file)
  217. }
  218. }
  219. }
  220. }
  221. //Convert all filepaths to slash
  222. for i := 0; i < len(files); i++ {
  223. files[i] = filepath.ToSlash(files[i])
  224. }
  225. return files, nil
  226. }
  227. /*
  228. Get Directory size, require filepath and include Hidden files option(true / false)
  229. Return total file size and file count
  230. */
  231. func GetDirctorySize(filename string, includeHidden bool) (int64, int) {
  232. var size int64 = 0
  233. var fileCount int = 0
  234. err := filepath.Walk(filename, func(thisFilename string, info os.FileInfo, err error) error {
  235. if err != nil {
  236. return err
  237. }
  238. if !info.IsDir() {
  239. if includeHidden {
  240. //append all into the file count and size
  241. size += info.Size()
  242. fileCount++
  243. } else {
  244. //Check if this is hidden
  245. if !IsInsideHiddenFolder(thisFilename) {
  246. size += info.Size()
  247. fileCount++
  248. }
  249. }
  250. }
  251. return err
  252. })
  253. if err != nil {
  254. return 0, fileCount
  255. }
  256. return size, fileCount
  257. }
  258. func GetFileDisplaySize(filesize int64, rounding int) string {
  259. precisionString := "%." + strconv.Itoa(rounding) + "f"
  260. bytes := float64(filesize)
  261. kilobytes := float64(bytes / 1024)
  262. if kilobytes < 1 {
  263. return fmt.Sprintf(precisionString, bytes) + "Bytes"
  264. }
  265. megabytes := float64(kilobytes / 1024)
  266. if megabytes < 1 {
  267. return fmt.Sprintf(precisionString, kilobytes) + "KB"
  268. }
  269. gigabytes := float64(megabytes / 1024)
  270. if gigabytes < 1 {
  271. return fmt.Sprintf(precisionString, megabytes) + "MB"
  272. }
  273. terabytes := float64(gigabytes / 1024)
  274. if terabytes < 1 {
  275. return fmt.Sprintf(precisionString, gigabytes) + "GB"
  276. }
  277. petabytes := float64(terabytes / 1024)
  278. if petabytes < 1 {
  279. return fmt.Sprintf(precisionString, terabytes) + "TB"
  280. }
  281. exabytes := float64(petabytes / 1024)
  282. if exabytes < 1 {
  283. return fmt.Sprintf(precisionString, petabytes) + "PB"
  284. }
  285. zettabytes := float64(exabytes / 1024)
  286. if zettabytes < 1 {
  287. return fmt.Sprintf(precisionString, exabytes) + "EB"
  288. }
  289. return fmt.Sprintf(precisionString, zettabytes) + "ZB"
  290. }
  291. func DecodeURI(inputPath string) string {
  292. inputPath = strings.ReplaceAll(inputPath, "+", "{{plus_sign}}")
  293. inputPath, _ = url.QueryUnescape(inputPath)
  294. inputPath = strings.ReplaceAll(inputPath, "{{plus_sign}}", "+")
  295. return inputPath
  296. }
  297. func GetMime(filename string) (string, string, error) {
  298. fileMime, err := mimetype.DetectFile(filename)
  299. if err != nil {
  300. return mime.TypeByExtension(filepath.Ext(filename)), filepath.Ext(filename), nil
  301. }
  302. return fileMime.String(), fileMime.Extension(), nil
  303. }
  304. func GetModTime(filepath string) (int64, error) {
  305. f, err := os.Open(filepath)
  306. if err != nil {
  307. return -1, err
  308. }
  309. statinfo, err := f.Stat()
  310. if err != nil {
  311. return -1, err
  312. }
  313. f.Close()
  314. return statinfo.ModTime().Unix(), nil
  315. }
  316. func UnderTheSameRoot(srcAbs string, destAbs string) (bool, error) {
  317. srcRoot, err := GetPhysicalRootFromPath(srcAbs)
  318. if err != nil {
  319. return false, err
  320. }
  321. destRoot, err := GetPhysicalRootFromPath(destAbs)
  322. if err != nil {
  323. return false, err
  324. }
  325. if srcRoot != "" && destRoot != "" {
  326. if srcRoot == destRoot {
  327. //apply fast move
  328. return true, nil
  329. }
  330. }
  331. return false, nil
  332. }
  333. //Get the physical root of a given filepath, e.g. C: or /home
  334. func GetPhysicalRootFromPath(filename string) (string, error) {
  335. filename, err := filepath.Abs(filename)
  336. if err != nil {
  337. return "", err
  338. }
  339. if filename[:1] == "/" {
  340. //Handle cases like /home/pi/foo.txt => return home
  341. filename = filename[1:]
  342. }
  343. filename = strings.TrimSpace(filename)
  344. if filename == "" {
  345. return "", nil
  346. }
  347. filename = filepath.ToSlash(filepath.Clean(filename))
  348. pathChunks := strings.Split(filename, "/")
  349. return pathChunks[0], nil
  350. }
  351. func GetFileSHA256Sum(filename string) (string, error) {
  352. f, err := os.Open(filename)
  353. if err != nil {
  354. return "", err
  355. }
  356. defer f.Close()
  357. h := sha256.New()
  358. if _, err := io.Copy(h, f); err != nil {
  359. return "", err
  360. }
  361. return hex.EncodeToString(h.Sum(nil)), nil
  362. }
  363. func GetFileMD5Sum(fsh *FileSystemHandler, rpath string) (string, error) {
  364. file, err := fsh.FileSystemAbstraction.ReadStream(rpath)
  365. if err != nil {
  366. return "", err
  367. }
  368. h := md5.New()
  369. if _, err := io.Copy(h, file); err != nil {
  370. return "", err
  371. }
  372. file.Close()
  373. return hex.EncodeToString(h.Sum(nil)), nil
  374. }