| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225 | package localversionimport (	"errors"	"os"	"path/filepath"	"sort"	"strings"	"time"	"imuslab.com/arozos/mod/filesystem")/*	localversion.go	This is a local version management module for arozos files	Author: tobychui*/type FileSnapshot struct {	HistoryID     string	Filename      string	ModTime       int64	OverwriteTime string	Filesize      int64	Relpath       string}type VersionList struct {	CurrentFile   string	LatestModtime int64	Versions      []*FileSnapshot}func GetFileVersionData(fsh *filesystem.FileSystemHandler, realFilepath string) (*VersionList, error) {	fshAbs := fsh.FileSystemAbstraction	mtime, _ := fshAbs.GetModTime(realFilepath)	versionList := VersionList{		CurrentFile:   filepath.Base(realFilepath),		LatestModtime: mtime,		Versions:      []*FileSnapshot{},	}	//Example folder structure: ./.localver/{date_time}/{file}	expectedVersionFiles := filepath.Join(filepath.Dir(realFilepath), ".metadata/.localver", "*", filepath.Base(realFilepath))	versions, err := fshAbs.Glob(filepath.ToSlash(expectedVersionFiles))	if err != nil {		return &versionList, err	}	//Reverse the versions so latest version on top	sort.Sort(sort.Reverse(sort.StringSlice(versions)))	for _, version := range versions {		historyID := filepath.Base(filepath.Dir(version))		mtime, _ := fshAbs.GetModTime(version)		overwriteDisplayTime := strings.ReplaceAll(strings.Replace(strings.Replace(historyID, "-", "/", 2), "-", ":", 2), "_", " ")		versionList.Versions = append(versionList.Versions, &FileSnapshot{			HistoryID:     historyID,			Filename:      filepath.Base(version),			ModTime:       mtime,			OverwriteTime: overwriteDisplayTime,			Filesize:      fshAbs.GetFileSize(version),			Relpath:       ".metadata/.localver/" + historyID + "/" + filepath.Base(version),		})	}	return &versionList, nil}func RestoreFileHistory(fsh *filesystem.FileSystemHandler, originalFilepath string, histroyID string) error {	fshAbs := fsh.FileSystemAbstraction	expectedVersionFile := filepath.Join(filepath.Dir(originalFilepath), ".metadata/.localver", filepath.Base(histroyID), filepath.Base(originalFilepath))	if !fshAbs.FileExists(expectedVersionFile) {		return errors.New("File version not exists")	}	//Restore it	fshAbs.Rename(originalFilepath, originalFilepath+".backup")	srcf, err := fshAbs.ReadStream(expectedVersionFile)	if err != nil {		return err	}	err = fshAbs.WriteStream(originalFilepath, srcf, 0775)	if err != nil {		srcf.Close()		return err	}	srcf.Close()	//Check if it has been restored correctly	versionFileHash, _ := filesystem.GetFileMD5Sum(fsh, expectedVersionFile)	copiedFileHash, _ := filesystem.GetFileMD5Sum(fsh, expectedVersionFile)	if versionFileHash != copiedFileHash {		//Rollback failed. Restore backup file		fshAbs.Rename(originalFilepath+".backup", originalFilepath)		return errors.New("Unable to restore file: file hash mismatch after restore")	}	//OK! Delete the backup file	fshAbs.Remove(originalFilepath + ".backup")	//Delete all history versions that is after the restored versions	expectedVersionFiles := filepath.Join(filepath.Dir(originalFilepath), ".metadata/.localver", "*", filepath.Base(originalFilepath))	versions, err := fshAbs.Glob(filepath.ToSlash(expectedVersionFiles))	if err != nil {		return err	}	enableRemoval := false	for _, version := range versions {		if enableRemoval {			//Remove this version as this is after the restored version			fshAbs.Remove(version)			fileInVersion, _ := fshAbs.Glob(filepath.ToSlash(filepath.Dir(version) + "/*"))			if len(fileInVersion) == 0 {				fshAbs.RemoveAll(filepath.Dir(version))			}		} else {			thisHistoryId := filepath.Base(filepath.Dir(version))			if thisHistoryId == histroyID {				//Match. Tag enable Removal				enableRemoval = true				//Remove this version				fshAbs.Remove(version)				fileInVersion, _ := fshAbs.Glob(filepath.ToSlash(filepath.Dir(version) + "/*"))				if len(fileInVersion) == 0 {					fshAbs.RemoveAll(filepath.Dir(version))				}			}		}	}	return nil}func RemoveFileHistory(fsh *filesystem.FileSystemHandler, originalFilepath string, histroyID string) error {	expectedVersionFile := filepath.Join(filepath.Dir(originalFilepath), ".metadata/.localver", filepath.Base(histroyID), filepath.Base(originalFilepath))	if !fsh.FileSystemAbstraction.FileExists(expectedVersionFile) {		return errors.New("File version not exists")	}	return fsh.FileSystemAbstraction.Remove(expectedVersionFile)}func RemoveAllRelatedFileHistory(fsh *filesystem.FileSystemHandler, originalFilepath string) error {	expectedVersionFiles, err := fsh.FileSystemAbstraction.Glob(filepath.Join(filepath.Dir(originalFilepath), ".metadata/.localver", "*", filepath.Base(originalFilepath)))	if err != nil {		return err	}	for _, version := range expectedVersionFiles {		os.Remove(version)	}	return nil}func CreateFileSnapshot(fsh *filesystem.FileSystemHandler, realFilepath string) error {	fshAbs := fsh.FileSystemAbstraction	if !fshAbs.FileExists(realFilepath) {		return errors.New("Source file not exists")	}	//Create the snapshot folder for this file	snapshotID := time.Now().Format("2006-01-02_15-04-05")	expectedSnapshotFolder := filepath.Join(filepath.Dir(realFilepath), ".metadata/.localver", snapshotID)	err := fshAbs.MkdirAll(expectedSnapshotFolder, 0775)	if err != nil {		return err	}	//Copy the target file to snapshot dir	targetVersionFilepath := filepath.Join(expectedSnapshotFolder, filepath.Base(realFilepath))	srcf, err := fshAbs.ReadStream(realFilepath)	if err != nil {		return err	}	defer srcf.Close()	return fshAbs.WriteStream(targetVersionFilepath, srcf, 0775)}//Clearn expired version backups that is older than maxReserveTimefunc CleanExpiredVersionBackups(fsh *filesystem.FileSystemHandler, walkRoot string, maxReserveTime int64) {	fshAbs := fsh.FileSystemAbstraction	localVerFolders := []string{}	fshAbs.Walk(walkRoot,		func(path string, info os.FileInfo, err error) error {			if err != nil {				//Skip this file				return nil			}			if !info.IsDir() && inLocalVersionFolder(path) {				//This is a file inside the localver folder. Check its modtime				mtime, _ := fshAbs.GetModTime(path)				if time.Now().Unix()-mtime > maxReserveTime {					//Too old! Remove this version history					fshAbs.Remove(path)				}				//Check if the folder still contains files. If not, remove it				files, _ := fshAbs.Glob(filepath.ToSlash(filepath.Dir(path)) + "/*")				if len(files) == 0 {					fshAbs.RemoveAll(filepath.Dir(path))				}			} else if info.IsDir() && filepath.Base(path) == ".localver" {				localVerFolders = append(localVerFolders, path)			}			return nil		})	for _, path := range localVerFolders {		//Check if a localver folder still contains folder. If not, delete this		files, _ := fshAbs.Glob(filepath.ToSlash(path) + "/*")		if len(files) == 0 {			fshAbs.RemoveAll(path)		}	}}func inLocalVersionFolder(path string) bool {	path = filepath.ToSlash(path)	return strings.Contains(path, "/.localver/") || filepath.Base(path) == ".localver"}
 |