static.go 10 KB

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