localversion.go 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. package localversion
  2. import (
  3. "errors"
  4. "os"
  5. "path/filepath"
  6. "sort"
  7. "strings"
  8. "time"
  9. "imuslab.com/arozos/mod/filesystem"
  10. )
  11. /*
  12. localversion.go
  13. This is a local version management module for arozos files
  14. Author: tobychui
  15. */
  16. type FileSnapshot struct {
  17. HistoryID string
  18. Filename string
  19. ModTime int64
  20. OverwriteTime string
  21. Filesize int64
  22. Relpath string
  23. }
  24. type VersionList struct {
  25. CurrentFile string
  26. LatestModtime int64
  27. Versions []*FileSnapshot
  28. }
  29. func GetFileVersionData(fsh *filesystem.FileSystemHandler, realFilepath string) (*VersionList, error) {
  30. fshAbs := fsh.FileSystemAbstraction
  31. mtime, _ := fshAbs.GetModTime(realFilepath)
  32. versionList := VersionList{
  33. CurrentFile: filepath.Base(realFilepath),
  34. LatestModtime: mtime,
  35. Versions: []*FileSnapshot{},
  36. }
  37. //Example folder structure: ./.localver/{date_time}/{file}
  38. expectedVersionFiles := filepath.Join(filepath.Dir(realFilepath), ".metadata/.localver", "*", filepath.Base(realFilepath))
  39. versions, err := fshAbs.Glob(filepath.ToSlash(expectedVersionFiles))
  40. if err != nil {
  41. return &versionList, err
  42. }
  43. //Reverse the versions so latest version on top
  44. sort.Sort(sort.Reverse(sort.StringSlice(versions)))
  45. for _, version := range versions {
  46. historyID := filepath.Base(filepath.Dir(version))
  47. mtime, _ := fshAbs.GetModTime(version)
  48. overwriteDisplayTime := strings.ReplaceAll(strings.Replace(strings.Replace(historyID, "-", "/", 2), "-", ":", 2), "_", " ")
  49. versionList.Versions = append(versionList.Versions, &FileSnapshot{
  50. HistoryID: historyID,
  51. Filename: filepath.Base(version),
  52. ModTime: mtime,
  53. OverwriteTime: overwriteDisplayTime,
  54. Filesize: fshAbs.GetFileSize(version),
  55. Relpath: ".metadata/.localver/" + historyID + "/" + filepath.Base(version),
  56. })
  57. }
  58. return &versionList, nil
  59. }
  60. func RestoreFileHistory(fsh *filesystem.FileSystemHandler, originalFilepath string, histroyID string) error {
  61. fshAbs := fsh.FileSystemAbstraction
  62. expectedVersionFile := filepath.Join(filepath.Dir(originalFilepath), ".metadata/.localver", filepath.Base(histroyID), filepath.Base(originalFilepath))
  63. if !fshAbs.FileExists(expectedVersionFile) {
  64. return errors.New("File version not exists")
  65. }
  66. //Restore it
  67. fshAbs.Rename(originalFilepath, originalFilepath+".backup")
  68. srcf, err := fshAbs.ReadStream(expectedVersionFile)
  69. if err != nil {
  70. return err
  71. }
  72. err = fshAbs.WriteStream(originalFilepath, srcf, 0775)
  73. if err != nil {
  74. srcf.Close()
  75. return err
  76. }
  77. srcf.Close()
  78. //Check if it has been restored correctly
  79. versionFileHash, _ := filesystem.GetFileMD5Sum(fsh, expectedVersionFile)
  80. copiedFileHash, _ := filesystem.GetFileMD5Sum(fsh, expectedVersionFile)
  81. if versionFileHash != copiedFileHash {
  82. //Rollback failed. Restore backup file
  83. fshAbs.Rename(originalFilepath+".backup", originalFilepath)
  84. return errors.New("Unable to restore file: file hash mismatch after restore")
  85. }
  86. //OK! Delete the backup file
  87. fshAbs.Remove(originalFilepath + ".backup")
  88. //Delete all history versions that is after the restored versions
  89. expectedVersionFiles := filepath.Join(filepath.Dir(originalFilepath), ".metadata/.localver", "*", filepath.Base(originalFilepath))
  90. versions, err := fshAbs.Glob(filepath.ToSlash(expectedVersionFiles))
  91. if err != nil {
  92. return err
  93. }
  94. enableRemoval := false
  95. for _, version := range versions {
  96. if enableRemoval {
  97. //Remove this version as this is after the restored version
  98. fshAbs.Remove(version)
  99. fileInVersion, _ := fshAbs.Glob(filepath.ToSlash(filepath.Dir(version) + "/*"))
  100. if len(fileInVersion) == 0 {
  101. fshAbs.RemoveAll(filepath.Dir(version))
  102. }
  103. } else {
  104. thisHistoryId := filepath.Base(filepath.Dir(version))
  105. if thisHistoryId == histroyID {
  106. //Match. Tag enable Removal
  107. enableRemoval = true
  108. //Remove this version
  109. fshAbs.Remove(version)
  110. fileInVersion, _ := fshAbs.Glob(filepath.ToSlash(filepath.Dir(version) + "/*"))
  111. if len(fileInVersion) == 0 {
  112. fshAbs.RemoveAll(filepath.Dir(version))
  113. }
  114. }
  115. }
  116. }
  117. return nil
  118. }
  119. func RemoveFileHistory(fsh *filesystem.FileSystemHandler, originalFilepath string, histroyID string) error {
  120. expectedVersionFile := filepath.Join(filepath.Dir(originalFilepath), ".metadata/.localver", filepath.Base(histroyID), filepath.Base(originalFilepath))
  121. if !fsh.FileSystemAbstraction.FileExists(expectedVersionFile) {
  122. return errors.New("File version not exists")
  123. }
  124. return fsh.FileSystemAbstraction.Remove(expectedVersionFile)
  125. }
  126. func RemoveAllRelatedFileHistory(fsh *filesystem.FileSystemHandler, originalFilepath string) error {
  127. expectedVersionFiles, err := fsh.FileSystemAbstraction.Glob(filepath.Join(filepath.Dir(originalFilepath), ".metadata/.localver", "*", filepath.Base(originalFilepath)))
  128. if err != nil {
  129. return err
  130. }
  131. for _, version := range expectedVersionFiles {
  132. os.Remove(version)
  133. }
  134. return nil
  135. }
  136. func CreateFileSnapshot(fsh *filesystem.FileSystemHandler, realFilepath string) error {
  137. fshAbs := fsh.FileSystemAbstraction
  138. if !fshAbs.FileExists(realFilepath) {
  139. return errors.New("Source file not exists")
  140. }
  141. //Create the snapshot folder for this file
  142. snapshotID := time.Now().Format("2006-01-02_15-04-05")
  143. expectedSnapshotFolder := filepath.Join(filepath.Dir(realFilepath), ".metadata/.localver", snapshotID)
  144. err := fshAbs.MkdirAll(expectedSnapshotFolder, 0775)
  145. if err != nil {
  146. return err
  147. }
  148. //Copy the target file to snapshot dir
  149. targetVersionFilepath := filepath.Join(expectedSnapshotFolder, filepath.Base(realFilepath))
  150. srcf, err := fshAbs.ReadStream(realFilepath)
  151. if err != nil {
  152. return err
  153. }
  154. defer srcf.Close()
  155. return fshAbs.WriteStream(targetVersionFilepath, srcf, 0775)
  156. }
  157. //Clearn expired version backups that is older than maxReserveTime
  158. func CleanExpiredVersionBackups(fsh *filesystem.FileSystemHandler, walkRoot string, maxReserveTime int64) {
  159. fshAbs := fsh.FileSystemAbstraction
  160. localVerFolders := []string{}
  161. fshAbs.Walk(walkRoot,
  162. func(path string, info os.FileInfo, err error) error {
  163. if err != nil {
  164. //Skip this file
  165. return nil
  166. }
  167. if !info.IsDir() && inLocalVersionFolder(path) {
  168. //This is a file inside the localver folder. Check its modtime
  169. mtime, _ := fshAbs.GetModTime(path)
  170. if time.Now().Unix()-mtime > maxReserveTime {
  171. //Too old! Remove this version history
  172. fshAbs.Remove(path)
  173. }
  174. //Check if the folder still contains files. If not, remove it
  175. files, _ := fshAbs.Glob(filepath.ToSlash(filepath.Dir(path)) + "/*")
  176. if len(files) == 0 {
  177. fshAbs.RemoveAll(filepath.Dir(path))
  178. }
  179. } else if info.IsDir() && filepath.Base(path) == ".localver" {
  180. localVerFolders = append(localVerFolders, path)
  181. }
  182. return nil
  183. })
  184. for _, path := range localVerFolders {
  185. //Check if a localver folder still contains folder. If not, delete this
  186. files, _ := fshAbs.Glob(filepath.ToSlash(path) + "/*")
  187. if len(files) == 0 {
  188. fshAbs.RemoveAll(path)
  189. }
  190. }
  191. }
  192. func inLocalVersionFolder(path string) bool {
  193. path = filepath.ToSlash(path)
  194. return strings.Contains(path, "/.localver/") || filepath.Base(path) == ".localver"
  195. }