Browse Source

Merge commit '2ba30fcc229a527cb271743d6cbb3a82a04466d5' into OAuth

AY 4 years ago
parent
commit
21b44a737d

+ 13 - 6
backup.go

@@ -143,14 +143,21 @@ func backup_listRestorable(w http.ResponseWriter, r *http.Request) {
 	if paretnfsh.Hierarchy == "user" {
 		//The file system is user based. Filter out those file that is not belong to this user
 		for _, restorableFile := range restorableReport.RestorableFiles {
-			fileAbsPath := filepath.Join(fsh.Path, restorableFile.RelpathOnDisk)
-			_, err := userinfo.RealPathToVirtualPath(fileAbsPath)
-			if err != nil {
-				//Cannot translate this file. That means the file is not owned by this user
-			} else {
-				//Can translate the path.
+			if restorableFile.IsSnapshot {
+				//Is snapshot. Always allow access
 				result.RestorableFiles = append(result.RestorableFiles, restorableFile)
+			} else {
+				//Is file
+				fileAbsPath := filepath.Join(fsh.Path, restorableFile.RelpathOnDisk)
+				_, err := userinfo.RealPathToVirtualPath(fileAbsPath)
+				if err != nil {
+					//Cannot translate this file. That means the file is not owned by this user
+				} else {
+					//Can translate the path.
+					result.RestorableFiles = append(result.RestorableFiles, restorableFile)
+				}
 			}
+
 		}
 	} else {
 		result = restorableReport

+ 1 - 0
documents/aroz version backup.drawio

@@ -0,0 +1 @@
+<mxfile host="Electron" modified="2021-08-04T15:38:00.121Z" agent="5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/13.7.3 Chrome/85.0.4183.98 Electron/10.1.2 Safari/537.36" etag="iStb3NhjgmRJ35-A-Wke" version="13.7.3" type="device"><diagram id="VnsJyidMvFq4PCg9yRLU" name="第1頁">3Zpdb+I4FIZ/DZdFsR2H5HIonRltNbvVMtJ09s4kDvFOiJFjCsyvX5s4H8b0gxUlBYSEfWKfxM955fgcMUC3i80XQZbZN57QfAC9ZDNAkwGEwIdwoL9esq0sOBhVhrlgiRnUGqbsNzVGz1hXLKGlNVBynku2tI0xLwoaS8tGhOBre1jKc/uuSzKnjmEak9y1/mCJzCprCEet/Stl86y+Mwii6sqC1IPNSsqMJHzdMaG7AboVnMuqtdjc0lzDq7lU8z4/c7V5MEEL+ZYJhCwW3+bf7+4fPk/K9d0//47vVzfGyxPJV2bB5mHltiZAi+STBql6BS+UcZzJRa56QDWr0TRxILZPBZq1KpFQvqBSbNWQdUsTG0JZB2RtEzQnkj3Z7okJ6rxx19zhgTN1Y+gZATZ+jPz8yLNdlHwlYmpmdem94gjsO5JEzKl0HKlGZ9mtaRecIwIFrzxQCHlDr/OBpwnby25BGA2j7uesIUVXHlIfoPcI6ctu+w0pdkJaitiJquCrIqHaj6fCuM6YpNMlifXVtXqL2lFOWZ7f8pyL3VyUEBqmsbKXUvBftHMliEM6SxtdPFEh6eZYZdQTAnu3g57pd5QTHFCO7z0vEov1sWCDKwWL+gY7ulKwzSbWF9jQAasOxyxliuIp6aZpCuODdJNgFuDgNHThR5Nt5NCd0JxKelq2YUwPs52F2Mfeadgi/4Mpt069XjqYHIX1BJBw+MzxuwNpdABSI9TTU3JTp2lBlmXG9VkFOMTU2qWNheRsXqh2rBhQJayxJsRU6vnJXFiwJNHTx4KW7DeZ7Vxp3kt9ftktCI8HeKJ9rSQvq+T5RMSbDLxO3IFLPDxAHL4bcDcFqoG7+rw02o1OX6B9SN/vR9vNTmra6PJpj6IPRtt3aP9NS8mFeqF5HZUHueY8E6o1lw2LC44ExvjVSAB41lC4OZxDuZOWxzkpSxbbzNXixfax2/mpmQ5x3Z1sDOOqt617GyYfdyM9CEy/mumj0PTbqbrTnflABVMMdJAn7dnk9fqAWtou9X7DkaBKn1879fZWcIjsTdTff/m/tcSA945kCO85eucyAnDT3bNr8EaJEESWCGGAL0KEo15FuFcodnaqN4tw70gARmcWoVsauHYR9qUZld1YJU1gKwhEwyD4fyJSk4cI7QtyCHBbIA3D88rKLYxcZsUJh/bRpfcaKXCLIldCtu+KSL2FX0cxbx9v78W8+sXSwbvbftT+Db2EpqzQnL2ZXoObA8lMJ0l/TP/6U/3krPilfhRXtxR4abmR79txOlTzAuBAoNDxgVLd9p8I1Vbf/p8D3f0H</diagram></mxfile>

BIN
documents/aroz version backup.png


+ 1 - 0
mod/disk/hybridBackup/compareRoots.go

@@ -47,6 +47,7 @@ func (t *BackupTask) compareRootPaths() ([]*RestorableFile, error) {
 				BackupDiskUID: t.DiskUID,
 				RemainingTime: 86400 - (time.Now().Unix() - value),
 				DeleteTime:    value,
+				IsSnapshot:    false,
 			}
 			results = append(results, &thisFile)
 		}

+ 9 - 0
mod/disk/hybridBackup/hybridBackup.go

@@ -63,6 +63,7 @@ type RestorableFile struct {
 	BackupDiskUID string //The UID of disk that is hold the backup of this file
 	RemainingTime int64  //Remaining time till auto remove
 	DeleteTime    int64  //Delete time
+	IsSnapshot    bool   //Define is this restorable file point to a snapshot instead
 }
 
 //The restorable report
@@ -287,6 +288,14 @@ func (m *Manager) ListRestorable(parentDiskID string) (RestorableReport, error)
 				diffFiles = append(diffFiles, restorable)
 			}
 		} else if task.Mode == "version" {
+			restorableFiles, err := listVersionRestorables(task)
+			if err != nil {
+				//Something went wrong. Skip this
+				continue
+			}
+			for _, restorable := range restorableFiles {
+				diffFiles = append(diffFiles, restorable)
+			}
 
 		} else {
 			//Unknown mode. Skip it

+ 64 - 0
mod/disk/hybridBackup/linker.go

@@ -0,0 +1,64 @@
+package hybridBackup
+
+import (
+	"encoding/json"
+	"io/ioutil"
+	"path/filepath"
+)
+
+/*
+	Linker.go
+
+	This script handle the linking file operations
+
+*/
+
+type LinkFileMap struct {
+	UnchangedFile map[string]string
+	DeletedFiles  map[string]string
+}
+
+//Generate and write link file to disk
+func generateLinkFile(snapshotFolder string, lf LinkFileMap) error {
+	js, err := json.MarshalIndent(lf, "", "\t")
+	if err != nil {
+		return err
+	}
+
+	return ioutil.WriteFile(filepath.Join(snapshotFolder, "snapshot.datalink"), js, 0755)
+}
+
+//Read link file and parse it into link file map
+func readLinkFile(snapshotFolder string) (*LinkFileMap, error) {
+	result := LinkFileMap{
+		UnchangedFile: map[string]string{},
+		DeletedFiles:  map[string]string{},
+	}
+
+	//Check if the link file exists
+	expectedLinkFilePath := filepath.Join(snapshotFolder, "snapshot.datalink")
+	if fileExists(expectedLinkFilePath) {
+		//Read the content of the link file
+		content, err := ioutil.ReadFile(expectedLinkFilePath)
+		if err == nil {
+			//No error. Read and parse the content
+			lfContent := LinkFileMap{}
+			err := json.Unmarshal(content, &lfContent)
+			if err == nil {
+				return &lfContent, nil
+			}
+		}
+	}
+
+	return &result, nil
+}
+
+//Check if a file exists in a linkFileMap. return boolean and its linked to snapshot name
+func (lfm *LinkFileMap) fileExists(fileRelPath string) (bool, string) {
+	val, ok := lfm.UnchangedFile[filepath.ToSlash(fileRelPath)]
+	if !ok {
+		return false, ""
+	} else {
+		return true, val
+	}
+}

+ 164 - 29
mod/disk/hybridBackup/versionBackup.go

@@ -2,7 +2,6 @@ package hybridBackup
 
 import (
 	"errors"
-	"io/ioutil"
 	"log"
 	"os"
 	"path/filepath"
@@ -38,15 +37,22 @@ func executeVersionBackup(backupConfig *BackupTask) (string, error) {
 	}
 
 	todayFolderName := time.Now().Format("2006-01-02")
-	previousSnapshotName, _ := getPreviousSnapshotName(backupConfig, todayFolderName)
+	previousSnapshotExists := true
+	previousSnapshotName, err := getPreviousSnapshotName(backupConfig, todayFolderName)
+	if err != nil {
+		previousSnapshotExists = false
+	}
 	snapshotLocation := filepath.Join(backupConfig.DiskPath, "/version/", todayFolderName)
 	previousSnapshotLocation := filepath.Join(backupConfig.DiskPath, "/version/", previousSnapshotName)
 
+	//Create today folder if not exist
 	if !fileExists(snapshotLocation) {
-		//Create today folder if not exist
 		os.MkdirAll(snapshotLocation, 0755)
 	}
 
+	//Read the previous snapshot datalink into a LinkFileMap and use binary search for higher performance
+	previousSnapshotMap, _ := readLinkFile(previousSnapshotLocation)
+
 	/*
 		Run a three pass compare logic between
 		1. source disk and new backup disk to check any new / modified files (created today)
@@ -54,6 +60,8 @@ func executeVersionBackup(backupConfig *BackupTask) (string, error) {
 		3. file in today backup disk no longer in the current source disk (created today, deleted today)
 	*/
 	copiedFileList := []string{}
+	linkedFileList := map[string]string{}
+	deletedFileList := map[string]string{}
 
 	//First pass: Check if there are any updated file from source and backup it to backup drive
 	fastWalk(parentRootAbs, func(filename string) error {
@@ -74,39 +82,57 @@ func executeVersionBackup(backupConfig *BackupTask) (string, error) {
 		yesterdayBackupLocation := filepath.Join(previousSnapshotLocation, relPath)
 
 		//Check if the file exists
-		if !fileExists(fileBackupLocation) && !fileExists(yesterdayBackupLocation) {
-			//File not exists in both current source and yesterday one. Copy it to the target location
-			if !isDir(fileBackupLocation) && fileExists(fileBackupLocation+".deleted") {
-				os.Remove(fileBackupLocation + ".deleted")
-			}
+		if !fileExists(yesterdayBackupLocation) {
+			//This file not in last snapshot location.
+			//Check if it is in previous snapshot map
+			fileFoundInSnapshotLinkFile, nameOfSnapshot := previousSnapshotMap.fileExists(relPath)
+			if fileFoundInSnapshotLinkFile {
+				//File found in the snapshot link file. Compare the one in snapshot
+				linkedSnapshotLocation := filepath.Join(backupConfig.DiskPath, "/version/", nameOfSnapshot)
+				linkedSnapshotOriginalFile := filepath.Join(linkedSnapshotLocation, relPath)
+				if fileExists(linkedSnapshotOriginalFile) {
+					//Linked file exists. Compare hash
+					fileHashMatch, err := fileHashIdentical(fileAbs, linkedSnapshotOriginalFile)
+					if err != nil {
+						return nil
+					}
 
-			if !fileExists(filepath.Dir(fileBackupLocation)) {
-				os.MkdirAll(filepath.Dir(fileBackupLocation), 0755)
-			}
+					if fileHashMatch {
+						//append this record to this snapshot linkdata file
+						linkedFileList[relPath] = nameOfSnapshot
+					} else {
+						//File hash mismatch. Do file copy to renew data
+						copyFileToBackupLocation(filename, fileBackupLocation)
+						copiedFileList = append(copiedFileList, fileBackupLocation)
+					}
+				} else {
+					//Invalid snapshot linkage. Assume new and do copy
+					log.Println("[HybridBackup] Link lost. Cloning source file to snapshot.")
+					copyFileToBackupLocation(filename, fileBackupLocation)
+					copiedFileList = append(copiedFileList, fileBackupLocation)
+				}
 
-			err = BufferedLargeFileCopy(filename, fileBackupLocation, 4096)
-			if err != nil {
-				log.Println("[HybridBackup] Failed to copy file: ", filepath.Base(filename)+". "+err.Error())
+			} else {
+				//This file is not in snapshot link file.
+				//This is new file. Copy it to backup
+				copyFileToBackupLocation(filename, fileBackupLocation)
+				copiedFileList = append(copiedFileList, fileBackupLocation)
 			}
 
-			copiedFileList = append(copiedFileList, fileBackupLocation)
-
 		} else if fileExists(yesterdayBackupLocation) {
 			//The file exists in the last snapshot
 			//Check if their hash is the same. If no, update it
-			srcHash, err := getFileHash(fileAbs)
+			fileHashMatch, err := fileHashIdentical(fileAbs, yesterdayBackupLocation)
 			if err != nil {
-				log.Println("[HybridBackup] Hash calculation failed for file "+filepath.Base(fileAbs), err.Error(), " Skipping.")
-				return nil
-			}
-			targetHash, err := getFileHash(yesterdayBackupLocation)
-			if err != nil {
-				log.Println("[HybridBackup] Hash calculation failed for file "+filepath.Base(fileBackupLocation), err.Error(), " Skipping.")
 				return nil
 			}
 
-			if srcHash != targetHash {
+			if !fileHashMatch {
 				//Hash mismatch. Overwrite the file
+				if !fileExists(filepath.Dir(fileBackupLocation)) {
+					os.MkdirAll(filepath.Dir(fileBackupLocation), 0755)
+				}
+
 				err = BufferedLargeFileCopy(filename, fileBackupLocation, 4096)
 				if err != nil {
 					log.Println("[HybridBackup] Copy Failed for file "+filepath.Base(fileAbs), err.Error(), " Skipping.")
@@ -114,6 +140,9 @@ func executeVersionBackup(backupConfig *BackupTask) (string, error) {
 					//No problem. Add this filepath into the list
 					copiedFileList = append(copiedFileList, fileBackupLocation)
 				}
+			} else {
+				//Create a link file for this relative path
+				linkedFileList[relPath] = previousSnapshotName
 			}
 		} else {
 			//Default case
@@ -133,6 +162,10 @@ func executeVersionBackup(backupConfig *BackupTask) (string, error) {
 
 				if srcHash != targetHash {
 					//Hash mismatch. Overwrite the file
+					if !fileExists(filepath.Dir(fileBackupLocation)) {
+						os.MkdirAll(filepath.Dir(fileBackupLocation), 0755)
+					}
+
 					err = BufferedLargeFileCopy(filename, fileBackupLocation, 4096)
 					if err != nil {
 						log.Println("[HybridBackup] Copy Failed for file "+filepath.Base(fileAbs), err.Error(), " Skipping.")
@@ -149,8 +182,12 @@ func executeVersionBackup(backupConfig *BackupTask) (string, error) {
 
 	//2nd pass: Check if there are anything exists in the previous backup but no longer exists in the source now
 	//For case where the file is backed up in previous snapshot but now the file has been removed
-	if fileExists(previousSnapshotLocation) {
+	if previousSnapshotExists {
 		fastWalk(previousSnapshotLocation, func(filename string) error {
+			if filepath.Base(filename) == "snapshot.datalink" {
+				//System reserved file. Skip this
+				return nil
+			}
 			//Get the target paste location
 			rootAbs, _ := filepath.Abs(previousSnapshotLocation)
 			fileAbs, _ := filepath.Abs(filename)
@@ -160,15 +197,25 @@ func executeVersionBackup(backupConfig *BackupTask) (string, error) {
 
 			relPath := strings.ReplaceAll(fileAbs, rootAbs, "")
 			sourcAssumeLocation := filepath.Join(parentRootAbs, relPath)
-			todaySnapshotLocation := filepath.Join(snapshotLocation, relPath)
+			//todaySnapshotLocation := filepath.Join(snapshotLocation, relPath)
 
 			if !fileExists(sourcAssumeLocation) {
 				//File exists in yesterday snapshot but not in the current source
 				//Assume it has been deleted, create a dummy indicator file
-				ioutil.WriteFile(todaySnapshotLocation+".deleted", []byte(""), 0755)
+				//ioutil.WriteFile(todaySnapshotLocation+".deleted", []byte(""), 0755)
+				deletedFileList[relPath] = todayFolderName
 			}
 			return nil
 		})
+
+		//Check for deleting of unchanged file as well
+		for relPath, _ := range previousSnapshotMap.UnchangedFile {
+			sourcAssumeLocation := filepath.Join(parentRootAbs, relPath)
+			if !fileExists(sourcAssumeLocation) {
+				//The source file no longer exists
+				deletedFileList[relPath] = todayFolderName
+			}
+		}
 	}
 
 	//3rd pass: Check if there are anything (except file with .deleted) in today backup drive that didn't exists in the source drive
@@ -179,7 +226,7 @@ func executeVersionBackup(backupConfig *BackupTask) (string, error) {
 			return nil
 		}
 
-		if filepath.Ext(filename) == ".deleted" {
+		if filepath.Ext(filename) == ".datalink" {
 			//Deleted file marker. Skip this
 			return nil
 		}
@@ -201,6 +248,16 @@ func executeVersionBackup(backupConfig *BackupTask) (string, error) {
 		return nil
 	})
 
+	//Generate linkfile for this snapshot
+	generateLinkFile(snapshotLocation, LinkFileMap{
+		UnchangedFile: linkedFileList,
+		DeletedFiles:  deletedFileList,
+	})
+
+	if err != nil {
+		return "", err
+	}
+
 	return "", nil
 }
 
@@ -216,7 +273,7 @@ func getPreviousSnapshotName(backupConfig *BackupTask, currentSnapshotName strin
 	existingSnapshots := []string{}
 	files, _ := filepath.Glob(filepath.ToSlash(filepath.Clean(backupRootAbs)) + "/*")
 	for _, file := range files {
-		if isDir(file) {
+		if isDir(file) && fileExists(filepath.Join(file, "snapshot.datalink")) {
 			existingSnapshots = append(existingSnapshots, filepath.Base(file))
 		}
 	}
@@ -245,3 +302,81 @@ func getPreviousSnapshotName(backupConfig *BackupTask, currentSnapshotName strin
 
 	return previousSnapshotName, nil
 }
+
+func copyFileToBackupLocation(filename string, fileBackupLocation string) error {
+	if !fileExists(filepath.Dir(fileBackupLocation)) {
+		os.MkdirAll(filepath.Dir(fileBackupLocation), 0755)
+	}
+
+	err := BufferedLargeFileCopy(filename, fileBackupLocation, 4096)
+	if err != nil {
+		log.Println("[HybridBackup] Failed to copy file: ", filepath.Base(filename)+". "+err.Error())
+		return err
+	}
+	return nil
+}
+
+func fileHashIdentical(srcFile string, matchingFile string) (bool, error) {
+	srcHash, err := getFileHash(srcFile)
+	if err != nil {
+		log.Println("[HybridBackup] Hash calculation failed for file "+filepath.Base(srcFile), err.Error(), " Skipping.")
+		return false, nil
+	}
+	targetHash, err := getFileHash(matchingFile)
+	if err != nil {
+		log.Println("[HybridBackup] Hash calculation failed for file "+filepath.Base(matchingFile), err.Error(), " Skipping.")
+		return false, nil
+	}
+
+	if srcHash != targetHash {
+		return false, nil
+	} else {
+		return true, nil
+	}
+}
+
+//List all restorable for version backup
+func listVersionRestorables(task *BackupTask) ([]*RestorableFile, error) {
+	//Check if mode is set correctly
+	restorableFiles := []*RestorableFile{}
+	if task.Mode != "version" {
+		return restorableFiles, errors.New("This task mode is not supported by this list function")
+	}
+
+	//List directories of the restorable snapshots
+	snapshotPath := filepath.ToSlash(filepath.Clean(filepath.Join(task.DiskPath, "/version/")))
+	filesInSnapshotFolder, err := filepath.Glob(snapshotPath + "/*")
+	if err != nil {
+		return restorableFiles, err
+	}
+
+	//Check if the foler is actually a snapshot
+	avaibleSnapshot := []string{}
+	for _, fileObject := range filesInSnapshotFolder {
+		possibleSnapshotDatalinkFile := filepath.Join(fileObject, "snapshot.datalink")
+		if fileExists(possibleSnapshotDatalinkFile) {
+			//This is a snapshot
+			avaibleSnapshot = append(avaibleSnapshot, fileObject)
+		}
+	}
+
+	//Build restorabe file struct for returning
+	for _, snapshot := range avaibleSnapshot {
+		thisFile := RestorableFile{
+			Filename:      filepath.Base(snapshot),
+			IsHidden:      false,
+			Filesize:      0,
+			RelpathOnDisk: filepath.ToSlash(snapshot),
+			RestorePoint:  task.ParentUID,
+			BackupDiskUID: task.DiskUID,
+			RemainingTime: -1,
+			DeleteTime:    -1,
+			IsSnapshot:    true,
+		}
+
+		restorableFiles = append(restorableFiles, &thisFile)
+	}
+
+	return restorableFiles, nil
+
+}

+ 76 - 13
web/SystemAO/disk/disk_restore.html

@@ -59,6 +59,12 @@
             overflow-y: scroll;
             padding-right: 4px;
         }
+
+        #snapshotList{
+            max-height: 300px;
+            overflow-y: scroll;
+            padding-right: 4px;
+        }
         
         .timeleft{
             width: 30px;
@@ -120,14 +126,22 @@
             <i class="icon remove"></i>
         </button>
        </div>
+       <div id="snapshotList" class="ui middle aligned divided list">
+            <div class="item">
+                <img class="ui mini image nointeract" src="../../img/system/drive-backup.svg">
+                <div class="content">
+                No Restorable Snapshot(s) for this virtual disk.
+                </div>
+            </div>
+        </div>
         <div id="restorableList" class="ui middle aligned divided list">
             <div class="item">
-              <img class="ui mini image nointeract" src="../../img/system/file-notfound.svg">
-              <div class="content">
+                <img class="ui mini image nointeract" src="../../img/system/file-notfound.svg">
+                <div class="content">
                 No Restorable File(s) for this virtual disk.
-              </div>
+                </div>
             </div>
-          </div>
+        </div>
         <div class="ui checkbox">
             <input type="checkbox" name="showhidden" autocomplete="false" onchange="toggleHiddenFiles(this.checked);">
             <label>Show hidden / cache files</label>
@@ -164,7 +178,12 @@
             filedata = JSON.parse(decodeURIComponent(filedata));
             console.log(filedata);
             //Show information on page
-            $("#fileinfo").find(".filename").text(filedata.Filename + ` (${ao_module_utils.formatBytes(filedata.Filesize, 2)})`);
+            if (filedata.IsSnapshot){
+                $("#fileinfo").find(".filename").text("Snapshot " + filedata.Filename);
+            }else{
+                $("#fileinfo").find(".filename").text(filedata.Filename + ` (${ao_module_utils.formatBytes(filedata.Filesize, 2)})`);
+            }
+            
             
             //Hide the filename in relpath
             var shortenRelpath = filedata.RelpathOnDisk.split("/");
@@ -176,6 +195,14 @@
             $("#fileinfo").find(".restorePoint").html('<i class="ui refresh icon"></i> Restore to ' + (filedata.RestorePoint));
             $("#fileinfo").find(".deleteRemainingTime").html('<i class="ui red remove icon"></i> Auto delete in '+ secondsToHms(filedata.RemainingTime));
 
+            if (filedata.IsSnapshot){
+                $("#fileinfo").find(".deleteRemainingTime").hide();
+                $("#fileinfo").find(".deleteTime").hide();
+            }else{
+                $("#fileinfo").find(".deleteRemainingTime").show();
+                $("#fileinfo").find(".deleteTime").show();
+            }
+
             $("#fileinfo").slideDown('fast');
         }
 
@@ -265,15 +292,49 @@
                     if (data.error !== undefined){
                         $("#error").find(".reason").texxt(data.error);
                     }else{
-                      
-                        //Sort the result by latest first
-                        data.RestorableFiles.sort(function(a, b) {
+
+                        //Filter out the restorable snapshot and files
+                        let restorableFiles = [];
+                        let restorableSnapshots = [];
+
+                        data.RestorableFiles.forEach(file => {
+                            if (file.IsSnapshot){
+                                restorableSnapshots.push(file);
+                            }else{
+                                restorableFiles.push(file);
+                                
+                            }
+                        });
+
+                        //Sort the result by latest first (file only)
+                        restorableFiles.sort(function(a, b) {
                             return b.DeleteTime - a.DeleteTime;
                         });
+                        restorableSnapshots.sort(function(a, b) {
+                            return b.Filename > a.Filename;
+                        });
 
-                        //Display the result
+                        //Display the reuslt for restorableSnapshot
+                        $("#snapshotList").html("");
+                        restorableSnapshots.forEach(snapshot => {
+                            let snapshotData = encodeURIComponent(JSON.stringify(snapshot));
+                            $("#snapshotList").append(`<div class="item restorableFile" filedata="${snapshotData}">
+                                <div class="right floated content">
+                                    <div class="ui tiny button" onclick="showFileInfo(this.parentNode.parentNode)">Info</div>
+                                    <div class="ui tiny green button" onclick="restoreThisFile($(this).parent().parent());">Restore</div>
+                                </div>
+                                
+                                <div class="content">
+                                    <p style="word-break: break-all; font-size: 97%"><img class="ui mini spaced image nointeract" src="../../img/system/drive-backup.svg">
+                                        Snapshot ${snapshot.Filename}
+                                    </p>
+                                </div>
+                            </div>`);
+                        })
+
+                        //Display the result for restorableFile
                         $("#restorableList").html("");
-                        data.RestorableFiles.forEach(fileObject => {
+                        restorableFiles.forEach(fileObject => {
                             let thisFileData = encodeURIComponent(JSON.stringify(fileObject));
 
                             var timerIcon = "100.svg";
@@ -321,7 +382,7 @@
                             </div>`);
                         });
 
-                        if (data.RestorableFiles.length == 0){
+                        if (restorableFiles.length == 0){
                             $("#restorableList").html(`<div class="item">
                             <img class="ui mini image nointeract" src="../../img/system/file-notfound.svg">
                             <div class="content">
@@ -330,10 +391,12 @@
                             </div>`);
 
                         }
-
-                        $(".hiddenfile").hide();
                     }
+                    
+
+                    $(".hiddenfile").hide();
                 }
+                
             })
         }