localversion.go 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  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(realFilepath string) (*VersionList, error) {
  30. mtime, _ := filesystem.GetModTime(realFilepath)
  31. versionList := VersionList{
  32. CurrentFile: filepath.Base(realFilepath),
  33. LatestModtime: mtime,
  34. Versions: []*FileSnapshot{},
  35. }
  36. //Example folder structure: ./.localver/{date_time}/{file}
  37. expectedVersionFiles := filepath.Join(filepath.Dir(realFilepath), ".localver", "*", filepath.Base(realFilepath))
  38. versions, err := filepath.Glob(filepath.ToSlash(expectedVersionFiles))
  39. if err != nil {
  40. return &versionList, err
  41. }
  42. //Reverse the versions so latest version on top
  43. sort.Sort(sort.Reverse(sort.StringSlice(versions)))
  44. for _, version := range versions {
  45. historyID := filepath.Base(filepath.Dir(version))
  46. mtime, _ := filesystem.GetModTime(version)
  47. overwriteDisplayTime := strings.ReplaceAll(strings.Replace(strings.Replace(historyID, "-", "/", 2), "-", ":", 2), "_", " ")
  48. versionList.Versions = append(versionList.Versions, &FileSnapshot{
  49. HistoryID: historyID,
  50. Filename: filepath.Base(version),
  51. ModTime: mtime,
  52. OverwriteTime: overwriteDisplayTime,
  53. Filesize: filesystem.GetFileSize(version),
  54. Relpath: ".localver/" + historyID + "/" + filepath.Base(version),
  55. })
  56. }
  57. return &versionList, nil
  58. }
  59. func RestoreFileHistory(originalFilepath string, histroyID string) error {
  60. expectedVersionFile := filepath.Join(filepath.Dir(originalFilepath), ".localver", filepath.Base(histroyID), filepath.Base(originalFilepath))
  61. if !filesystem.FileExists(expectedVersionFile) {
  62. return errors.New("File version not exists")
  63. }
  64. //Restore it
  65. os.Rename(originalFilepath, originalFilepath+".backup")
  66. filesystem.BasicFileCopy(expectedVersionFile, originalFilepath)
  67. //Check if it has been restored correctly
  68. versionFileHash, _ := filesystem.GetFileMD5Sum(expectedVersionFile)
  69. copiedFileHash, _ := filesystem.GetFileMD5Sum(expectedVersionFile)
  70. if versionFileHash != copiedFileHash {
  71. //Rollback failed. Restore backup file
  72. os.Rename(originalFilepath+".backup", originalFilepath)
  73. return errors.New("Unable to restore file: file hash mismatch after restore")
  74. }
  75. //OK! Delete the backup file
  76. os.Remove(originalFilepath + ".backup")
  77. //Delete all history versions that is after the restored versions
  78. expectedVersionFiles := filepath.Join(filepath.Dir(originalFilepath), ".localver", "*", filepath.Base(originalFilepath))
  79. versions, err := filepath.Glob(filepath.ToSlash(expectedVersionFiles))
  80. if err != nil {
  81. return err
  82. }
  83. enableRemoval := false
  84. for _, version := range versions {
  85. if enableRemoval {
  86. //Remove this version as this is after the restored version
  87. os.Remove(version)
  88. } else {
  89. thisHistoryId := filepath.Base(filepath.Dir(version))
  90. if thisHistoryId == histroyID {
  91. //Match. Tag enable Removal
  92. enableRemoval = true
  93. //Remove this version
  94. os.Remove(version)
  95. }
  96. }
  97. }
  98. return nil
  99. }
  100. func RemoveFileHistory(originalFilepath string, histroyID string) error {
  101. expectedVersionFile := filepath.Join(filepath.Dir(originalFilepath), ".localver", filepath.Base(histroyID), filepath.Base(originalFilepath))
  102. if !filesystem.FileExists(expectedVersionFile) {
  103. return errors.New("File version not exists")
  104. }
  105. return os.Remove(expectedVersionFile)
  106. }
  107. func RemoveAllRelatedFileHistory(originalFilepath string) error {
  108. expectedVersionFiles, err := filepath.Glob(filepath.Join(filepath.Dir(originalFilepath), ".localver", "*", filepath.Base(originalFilepath)))
  109. if err != nil {
  110. return err
  111. }
  112. for _, version := range expectedVersionFiles {
  113. os.Remove(version)
  114. }
  115. return nil
  116. }
  117. func CreateFileSnapshot(realFilepath string) error {
  118. if !filesystem.FileExists(realFilepath) {
  119. return errors.New("Source file not exists")
  120. }
  121. //Create the snapshot folder for this file
  122. snapshotID := time.Now().Format("2006-01-02_15-04-05")
  123. expectedSnapshotFolder := filepath.Join(filepath.Dir(realFilepath), ".localver", snapshotID)
  124. os.MkdirAll(expectedSnapshotFolder, 0775)
  125. //Copy the target file to snapshot dir
  126. targetVersionFilepath := filepath.Join(expectedSnapshotFolder, filepath.Base(realFilepath))
  127. return filesystem.BasicFileCopy(realFilepath, targetVersionFilepath)
  128. }
  129. //Clearn expired version backups that is older than maxReserveTime
  130. func CleanExpiredVersionBackups(walkRoot string, maxReserveTime int64) {
  131. localVerFolders := []string{}
  132. filepath.Walk(walkRoot,
  133. func(path string, info os.FileInfo, err error) error {
  134. if err != nil {
  135. //Skip this file
  136. return nil
  137. }
  138. if !info.IsDir() && inLocalVersionFolder(path) {
  139. //This is a file inside the localver folder. Check its modtime
  140. mtime, _ := filesystem.GetModTime(path)
  141. if time.Now().Unix()-mtime > maxReserveTime {
  142. //Too old! Remove this version history
  143. os.Remove(path)
  144. }
  145. //Check if the folder still contains files. If not, remove it
  146. files, _ := filepath.Glob(filepath.ToSlash(filepath.Dir(path)) + "/*")
  147. if len(files) == 0 {
  148. os.RemoveAll(filepath.Dir(path))
  149. }
  150. } else if info.IsDir() && filepath.Base(path) == ".localver" {
  151. localVerFolders = append(localVerFolders, path)
  152. }
  153. return nil
  154. })
  155. for _, path := range localVerFolders {
  156. //Check if a localver folder still contains folder. If not, delete this
  157. files, _ := filepath.Glob(filepath.ToSlash(path) + "/*")
  158. if len(files) == 0 {
  159. os.RemoveAll(path)
  160. }
  161. }
  162. }
  163. func inLocalVersionFolder(path string) bool {
  164. path = filepath.ToSlash(path)
  165. return strings.Contains(path, "/.localver/") || filepath.Base(path) == ".localver"
  166. }