static.go 12 KB

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