static.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439
  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 = arozfs.ToSlash(filepath.Clean(vpath))
  93. tmp := strings.Split(vpath, ":")
  94. vdID := tmp[0]
  95. if strings.HasPrefix(vdID, "./") {
  96. //For newer go version where Clean return with ./ prefix
  97. vdID = strings.TrimPrefix(vdID, "./")
  98. }
  99. pathSlice := tmp[1:]
  100. path := strings.Join(pathSlice, ":")
  101. return vdID, path, nil
  102. }
  103. func GetFileDataFromPath(fsh *FileSystemHandler, vpath string, realpath string, sizeRounding int) FileData {
  104. fileSize := fsh.FileSystemAbstraction.GetFileSize(realpath)
  105. displaySize := GetFileDisplaySize(fileSize, sizeRounding)
  106. modtime, _ := fsh.FileSystemAbstraction.GetModTime(realpath)
  107. var shortcutInfo *arozfs.ShortcutData = nil
  108. if filepath.Ext(realpath) == ".shortcut" {
  109. shortcutContent, err := fsh.FileSystemAbstraction.ReadFile(realpath)
  110. if err != nil {
  111. shortcutInfo = nil
  112. } else {
  113. shortcutInfo, err = shortcut.ReadShortcut(shortcutContent)
  114. if err != nil {
  115. shortcutInfo = nil
  116. }
  117. }
  118. }
  119. return FileData{
  120. Filename: filepath.Base(realpath),
  121. Filepath: vpath,
  122. Realpath: filepath.ToSlash(realpath),
  123. IsDir: fsh.FileSystemAbstraction.IsDir(realpath),
  124. Filesize: fileSize,
  125. Displaysize: displaySize,
  126. ModTime: modtime,
  127. IsShared: false,
  128. Shortcut: shortcutInfo,
  129. }
  130. }
  131. func CheckMounted(mountpoint string) bool {
  132. if runtime.GOOS == "windows" {
  133. //Windows
  134. //Check if the given folder exists
  135. info, err := os.Stat(mountpoint)
  136. if os.IsNotExist(err) {
  137. return false
  138. }
  139. return info.IsDir()
  140. } else {
  141. //Linux
  142. cmd := exec.Command("mountpoint", mountpoint)
  143. out, err := cmd.CombinedOutput()
  144. if err != nil {
  145. return false
  146. }
  147. outstring := strings.TrimSpace(string(out))
  148. if strings.Contains(outstring, " is a mountpoint") {
  149. return true
  150. } else {
  151. return false
  152. }
  153. }
  154. }
  155. func MountDevice(mountpt string, mountdev string, filesystem string) error {
  156. //Check if running under sudo mode and in linux
  157. if runtime.GOOS == "linux" {
  158. //Try to mount the file system
  159. if mountdev == "" {
  160. return errors.New("Disk with automount enabled has no mountdev value: " + mountpt)
  161. }
  162. if mountpt == "" {
  163. return errors.New("Invalid storage.json. Mount point not given or not exists for " + mountdev)
  164. }
  165. //Check if device exists
  166. if !FileExists(mountdev) {
  167. //Device driver not exists.
  168. return errors.New("Device not exists: " + mountdev)
  169. }
  170. //Mount the device
  171. if CheckMounted(mountpt) {
  172. log.Println(mountpt + " already mounted.")
  173. } else {
  174. log.Println("Mounting " + mountdev + "(" + filesystem + ") to " + filepath.Clean(mountpt))
  175. cmd := exec.Command("mount", "-t", filesystem, mountdev, filepath.Clean(mountpt))
  176. cmd.Stdout = os.Stdout
  177. cmd.Stderr = os.Stderr
  178. cmd.Run()
  179. }
  180. //Check if the path exists
  181. if !FileExists(mountpt) {
  182. //Mounted but path still not found. Skip this device
  183. return errors.New("Unable to find " + mountpt)
  184. }
  185. } else {
  186. return errors.New("Unsupported platform")
  187. }
  188. return nil
  189. }
  190. func GetFileSize(filename string) int64 {
  191. fi, err := os.Stat(filename)
  192. if err != nil {
  193. return 0
  194. }
  195. // get the size
  196. return fi.Size()
  197. }
  198. func IsInsideHiddenFolder(path string) bool {
  199. thisPathInfo := filepath.ToSlash(filepath.Clean(path))
  200. pathData := strings.Split(thisPathInfo, "/")
  201. for _, thispd := range pathData {
  202. if len(thispd) > 0 && thispd[:1] == "." {
  203. //This path contain one of the folder is hidden
  204. return true
  205. }
  206. }
  207. return false
  208. }
  209. /*
  210. Wildcard Replacement Glob, design to hanle path with [ or ] inside.
  211. You can also pass in normal path for globing if you are not sure.
  212. */
  213. func WGlob(path string) ([]string, error) {
  214. files, err := filepath.Glob(path)
  215. if err != nil {
  216. return []string{}, err
  217. }
  218. if strings.Contains(path, "[") == true || strings.Contains(path, "]") == true {
  219. if len(files) == 0 {
  220. //Handle reverse check. Replace all [ and ] with ?
  221. newSearchPath := strings.ReplaceAll(path, "[", "?")
  222. newSearchPath = strings.ReplaceAll(newSearchPath, "]", "?")
  223. //Scan with all the similar structure except [ and ]
  224. tmpFilelist, _ := filepath.Glob(newSearchPath)
  225. for _, file := range tmpFilelist {
  226. file = filepath.ToSlash(file)
  227. if strings.Contains(file, filepath.ToSlash(filepath.Dir(path))) {
  228. files = append(files, file)
  229. }
  230. }
  231. }
  232. }
  233. //Convert all filepaths to slash
  234. for i := 0; i < len(files); i++ {
  235. files[i] = filepath.ToSlash(files[i])
  236. }
  237. return files, nil
  238. }
  239. /*
  240. Get Directory size, require filepath and include Hidden files option(true / false)
  241. Return total file size and file count
  242. */
  243. func GetDirctorySize(filename string, includeHidden bool) (int64, int) {
  244. var size int64 = 0
  245. var fileCount int = 0
  246. err := filepath.Walk(filename, func(thisFilename string, info os.FileInfo, err error) error {
  247. if err != nil {
  248. return err
  249. }
  250. if !info.IsDir() {
  251. if includeHidden {
  252. //append all into the file count and size
  253. size += info.Size()
  254. fileCount++
  255. } else {
  256. //Check if this is hidden
  257. if !IsInsideHiddenFolder(thisFilename) {
  258. size += info.Size()
  259. fileCount++
  260. }
  261. }
  262. }
  263. return err
  264. })
  265. if err != nil {
  266. return 0, fileCount
  267. }
  268. return size, fileCount
  269. }
  270. func GetFileDisplaySize(filesize int64, rounding int) string {
  271. precisionString := "%." + strconv.Itoa(rounding) + "f"
  272. bytes := float64(filesize)
  273. kilobytes := float64(bytes / 1024)
  274. if kilobytes < 1 {
  275. return fmt.Sprintf(precisionString, bytes) + "Bytes"
  276. }
  277. megabytes := float64(kilobytes / 1024)
  278. if megabytes < 1 {
  279. return fmt.Sprintf(precisionString, kilobytes) + "KB"
  280. }
  281. gigabytes := float64(megabytes / 1024)
  282. if gigabytes < 1 {
  283. return fmt.Sprintf(precisionString, megabytes) + "MB"
  284. }
  285. terabytes := float64(gigabytes / 1024)
  286. if terabytes < 1 {
  287. return fmt.Sprintf(precisionString, gigabytes) + "GB"
  288. }
  289. petabytes := float64(terabytes / 1024)
  290. if petabytes < 1 {
  291. return fmt.Sprintf(precisionString, terabytes) + "TB"
  292. }
  293. exabytes := float64(petabytes / 1024)
  294. if exabytes < 1 {
  295. return fmt.Sprintf(precisionString, petabytes) + "PB"
  296. }
  297. zettabytes := float64(exabytes / 1024)
  298. if zettabytes < 1 {
  299. return fmt.Sprintf(precisionString, exabytes) + "EB"
  300. }
  301. return fmt.Sprintf(precisionString, zettabytes) + "ZB"
  302. }
  303. func DecodeURI(inputPath string) string {
  304. inputPath = strings.ReplaceAll(inputPath, "+", "{{plus_sign}}")
  305. inputPath, _ = url.QueryUnescape(inputPath)
  306. inputPath = strings.ReplaceAll(inputPath, "{{plus_sign}}", "+")
  307. return inputPath
  308. }
  309. func GetMime(filename string) (string, string, error) {
  310. fileMime, err := mimetype.DetectFile(filename)
  311. if err != nil {
  312. return mime.TypeByExtension(filepath.Ext(filename)), filepath.Ext(filename), nil
  313. }
  314. return fileMime.String(), fileMime.Extension(), nil
  315. }
  316. func GetModTime(filepath string) (int64, error) {
  317. f, err := os.Open(filepath)
  318. if err != nil {
  319. return -1, err
  320. }
  321. statinfo, err := f.Stat()
  322. if err != nil {
  323. return -1, err
  324. }
  325. f.Close()
  326. return statinfo.ModTime().Unix(), nil
  327. }
  328. func UnderTheSameRoot(srcAbs string, destAbs string) (bool, error) {
  329. srcRoot, err := GetPhysicalRootFromPath(srcAbs)
  330. if err != nil {
  331. return false, err
  332. }
  333. destRoot, err := GetPhysicalRootFromPath(destAbs)
  334. if err != nil {
  335. return false, err
  336. }
  337. if srcRoot != "" && destRoot != "" {
  338. if srcRoot == destRoot {
  339. //apply fast move
  340. return true, nil
  341. }
  342. }
  343. return false, nil
  344. }
  345. // Get the physical root of a given filepath, e.g. C: or /home
  346. func GetPhysicalRootFromPath(filename string) (string, error) {
  347. filename, err := filepath.Abs(filename)
  348. if err != nil {
  349. return "", err
  350. }
  351. if filename[:1] == "/" {
  352. //Handle cases like /home/pi/foo.txt => return home
  353. filename = filename[1:]
  354. }
  355. filename = strings.TrimSpace(filename)
  356. if filename == "" {
  357. return "", nil
  358. }
  359. filename = filepath.ToSlash(filepath.Clean(filename))
  360. pathChunks := strings.Split(filename, "/")
  361. return pathChunks[0], nil
  362. }
  363. func GetFileSHA256Sum(filename string) (string, error) {
  364. f, err := os.Open(filename)
  365. if err != nil {
  366. return "", err
  367. }
  368. defer f.Close()
  369. h := sha256.New()
  370. if _, err := io.Copy(h, f); err != nil {
  371. return "", err
  372. }
  373. return hex.EncodeToString(h.Sum(nil)), nil
  374. }
  375. func GetFileMD5Sum(fsh *FileSystemHandler, rpath string) (string, error) {
  376. file, err := fsh.FileSystemAbstraction.ReadStream(rpath)
  377. if err != nil {
  378. return "", err
  379. }
  380. h := md5.New()
  381. if _, err := io.Copy(h, file); err != nil {
  382. return "", err
  383. }
  384. file.Close()
  385. return hex.EncodeToString(h.Sum(nil)), nil
  386. }