瀏覽代碼

Added localization to file version control

Toby Chui 3 年之前
父節點
當前提交
cb136e840c

+ 63 - 5
file_system.go

@@ -209,6 +209,9 @@ func FileSystemInit() {
 	shareManager.ValidateAndClearShares()
 	nightlyManager.RegisterNightlyTask(shareManager.ValidateAndClearShares)
 
+	//Clear file version history that is more than 30 days
+	system_fs_clearVersionHistories()
+	nightlyManager.RegisterNightlyTask(system_fs_clearVersionHistories)
 }
 
 /*
@@ -2541,13 +2544,15 @@ func system_fs_FileVersionHistory(w http.ResponseWriter, r *http.Request) {
 	}
 
 	opr, _ := mv(r, "opr", true)
+
+	rpath, err := userinfo.VirtualPathToRealPath(path)
+	if err != nil {
+		sendErrorResponse(w, "Unable to translate virtual path")
+		return
+	}
+
 	if opr == "" {
 		//List file history
-		rpath, err := userinfo.VirtualPathToRealPath(path)
-		if err != nil {
-			sendErrorResponse(w, "Unable to translate virtual path")
-			return
-		}
 
 		fileVersionData, err := localversion.GetFileVersionData(rpath)
 		if err != nil {
@@ -2560,14 +2565,67 @@ func system_fs_FileVersionHistory(w http.ResponseWriter, r *http.Request) {
 
 	} else if opr == "delete" {
 		//Delete file history of given history ID
+		historyID, err := mv(r, "histid", true)
+		if err != nil {
+			sendErrorResponse(w, "Invalid history id given")
+			return
+		}
+
+		err = localversion.RemoveFileHistory(rpath, historyID)
+		if err != nil {
+			sendErrorResponse(w, err.Error())
+			return
+		}
+
+		sendOK(w)
+	} else if opr == "deleteAll" {
+		//Delete all file history of given vpath
+		err = localversion.RemoveAllRelatedFileHistory(rpath)
+		if err != nil {
+			sendErrorResponse(w, err.Error())
+			return
+		}
+
+		sendOK(w)
 
 	} else if opr == "restore" {
 		//Restore file history of given history ID
+		historyID, err := mv(r, "histid", true)
+		if err != nil {
+			sendErrorResponse(w, "Invalid history id given")
+			return
+		}
+		err = localversion.RestoreFileHistory(rpath, historyID)
+		if err != nil {
+			sendErrorResponse(w, err.Error())
+			return
+		}
+
+		sendOK(w)
+	} else if opr == "new" {
+		//Create a new snapshot of this file
+		err = localversion.CreateFileSnapshot(rpath)
+		if err != nil {
+			sendErrorResponse(w, err.Error())
+			return
+		}
 
+		sendOK(w)
+	} else {
+		sendErrorResponse(w, "Unknown opr")
 	}
 
 }
 
+func system_fs_clearVersionHistories() {
+	for _, fsh := range fsHandlers {
+		if fsh.Filesystem != "virtual" && !fsh.ReadOnly {
+			localversion.CleanExpiredVersionBackups(fsh.Path, 30*86400)
+		}
+
+	}
+}
+
 //Translate path from and to virtual and realpath
 func system_fs_handlePathTranslate(w http.ResponseWriter, r *http.Request) {
 	userinfo, err := userHandler.GetUserInfoFromRequest(w, r)

+ 66 - 1
mod/filesystem/localversion/localversion.go

@@ -6,6 +6,7 @@ import (
 	"path/filepath"
 	"sort"
 	"strings"
+	"time"
 
 	"imuslab.com/arozos/mod/filesystem"
 )
@@ -118,10 +119,74 @@ func RestoreFileHistory(originalFilepath string, histroyID string) error {
 }
 
 func RemoveFileHistory(originalFilepath string, histroyID string) error {
+	expectedVersionFile := filepath.Join(filepath.Dir(originalFilepath), ".localver", filepath.Base(histroyID), filepath.Base(originalFilepath))
+	if !filesystem.FileExists(expectedVersionFile) {
+		return errors.New("File version not exists")
+	}
+
+	return os.Remove(expectedVersionFile)
+}
+
+func RemoveAllRelatedFileHistory(originalFilepath string) error {
+	expectedVersionFiles, err := filepath.Glob(filepath.Join(filepath.Dir(originalFilepath), ".localver", "*", filepath.Base(originalFilepath)))
+	if err != nil {
+		return err
+	}
+	for _, version := range expectedVersionFiles {
+		os.Remove(version)
+	}
 	return nil
 }
 
 func CreateFileSnapshot(realFilepath string) error {
+	if !filesystem.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), ".localver", snapshotID)
+	os.MkdirAll(expectedSnapshotFolder, 0775)
+
+	//Copy the target file to snapshot dir
+	targetVersionFilepath := filepath.Join(expectedSnapshotFolder, filepath.Base(realFilepath))
+	return filesystem.BasicFileCopy(realFilepath, targetVersionFilepath)
+}
 
-	return nil
+//Clearn expired version backups that is older than maxReserveTime
+func CleanExpiredVersionBackups(walkRoot string, maxReserveTime int64) {
+	localVerFolders := []string{}
+	filepath.Walk(walkRoot,
+		func(path string, info os.FileInfo, err error) error {
+			if !info.IsDir() && inLocalVersionFolder(path) {
+				//This is a file inside the localver folder. Check its modtime
+				mtime, _ := filesystem.GetModTime(path)
+				if time.Now().Unix()-mtime > maxReserveTime {
+					//Too old! Remove this version history
+					os.Remove(path)
+				}
+
+				//Check if the folder still contains files. If not, remove it
+				files, _ := filepath.Glob(filepath.ToSlash(filepath.Dir(path)) + "/*")
+				if len(files) == 0 {
+					os.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, _ := filepath.Glob(filepath.ToSlash(path) + "/*")
+		if len(files) == 0 {
+			os.RemoveAll(path)
+		}
+	}
+}
+
+func inLocalVersionFolder(path string) bool {
+	path = filepath.ToSlash(path)
+	return strings.Contains(path, "/.localver/") || filepath.Base(path) == ".localver"
 }

+ 126 - 8
web/SystemAO/file_system/file_versions.html

@@ -9,9 +9,6 @@
         <script type="text/javascript" src="../../script/ao_module.js"></script>
         <script type="text/javascript" src="../../script/applocale.js"></script>
         <style>
-            body{
-                overflow:hidden;
-            }
 
         </style>
     </head>
@@ -22,7 +19,12 @@
                 <span locale="title/title">File Version History</span>
                 <div class="sub header" locale="title/desc">Any file versions that is more than 30 days old will be automatically removed from the system.</div>
             </h3>
+            <button onclick="createSnapshot();" class="ui basic green button"><i class="ui add icon"></i> <span locale="button/newver">Create New Version</span></button>
+            
             <div class="ui divider"></div>
+            <div id="succ" style="display:none;" class="ui green message">
+                <i class="ui checkmark icon"></i> <span id="msg">File Restore Succeed</span>
+            </div>
             <div>
                 <table class="ui very basic fluid celled table unstackable">
                     <tbody id="versions">
@@ -30,8 +32,8 @@
                         <td>
                           <h4 class="ui header">
                                 <div class="content">
-                                <span>No File</span>
-                                <div class="sub header">Invalid usage</div>
+                                <span locale="message/nofile/title">No File</span>
+                                <div locale="message/nofile/desc" class="sub header">Invalid usage</div>
                             </div>
                             </h4>
                         </td>
@@ -39,20 +41,49 @@
                     </tbody>
                   </table>
             </div>
+            <div class="ui divider"></div>
+            <button class="ui red  button" onclick="deleteAllVersions();"><i class="ui trash icon"></i> <span locale="button/removeAll">Remove All Version Histories</span></button>
             <br>
         </div>
         <script>
             var files = ao_module_loadInputFiles();
             var targetFile = "";
+            var currentVersionList = [];
             if (files.length >= 1){
                 targetFile = files[0];
                 loadVersionHistory(targetFile);
+
+                setInterval(function(){
+                    checkHistoryListUpdate(targetFile);
+                }, 3000)
             }else{
                 //No file selected
 
             }
 
+            applocale.init("../../SystemAO/locale/file_versions.json", function(){
+                applocale.translate();
+            });
+
+          
+
+            function checkHistoryListUpdate(vpath){
+                $.ajax({
+                    url: "../../system/file_system/versionHistory",
+                    data: {path: vpath},
+                    method: "POST",
+                    success: function(data){
+                        if (data.error == undefined){
+                            if (currentVersionList.length != data.Versions.length){
+                                loadVersionHistory(vpath);
+                            }
+                        }
+                    }
+                });
+            }
+
             function loadVersionHistory(vpath){
+                $("#versions").html("Loading...");
                 $.ajax({
                     url: "../../system/file_system/versionHistory",
                     data: {path: vpath},
@@ -62,6 +93,7 @@
                             alert(data.error);
                         }else{
                             $("#versions").html("");
+                            currentVersionList = data.Versions;
                             data.Versions.forEach(fileVersionEntry => {
                                 var filesize = ao_module_utils.formatBytes(fileVersionEntry.Filesize, 1);
                                 $("#versions").append(`<tr>
@@ -74,14 +106,28 @@
                                     </h4></td>
                                     <td>
                                         <div class="ui icon mini buttons">
-                                            <button relpath="${fileVersionEntry.Relpath}" onclick="downloadVersion(this);" class="ui very basic icon button" title="Download Version"><i class="ui blue download icon"></i></button>
-                                            <button verid="${fileVersionEntry.HistoryID}" class="ui very basic icon button" title="Restore This Version"><i class="ui green sync icon"></i></button>
-                                            <button verid="${fileVersionEntry.HistoryID}" class="ui very basic icon button" title="Delete"><i class="ui red trash icon"></i></button>
+                                            <button relpath="${fileVersionEntry.Relpath}" onclick="downloadVersion(this);" class="ui very basic icon button" title="${applocale.getString("title/download", "Download Version")}"><i class="ui blue download icon"></i></button>
+                                            <button verid="${fileVersionEntry.HistoryID}" onclick="restoreVersion(this);" class="ui very basic icon button" title="${applocale.getString("title/restore", "Restore This Version")}"><i class="ui green history icon"></i></button>
+                                            <button verid="${fileVersionEntry.HistoryID}" onclick="deleteVersion(this);" class="ui very basic icon button" title="${applocale.getString("title/delete", "Delete")}"><i class="ui red trash icon"></i></button>
                                         </div>
                                         
                                     </td>
                                 </tr>`);
                             });
+
+                            if (data.Versions.length == 0){
+                                //This file has no version history
+                                $("#versions").html(`<tr>
+                                    <td>
+                                        <h4 class="ui header">
+                                            <div class="content">
+                                                <i class="ui history icon"></i><span> ${applocale.getString("message/nohist/title", "No History")}</span>
+                                                <div class="sub header">${applocale.getString("message/nohist/desc", "This file has no previous version histories.")}</div>
+                                            </div>
+                                        </h4>
+                                    </td>
+                                </tr>`);
+                            }
                         }
                     }
                 });
@@ -95,8 +141,80 @@
                 window.open("../../media?file=" + accessPath + "&download=true")
             }
 
+            function createSnapshot(){
+                //Create a snapshot of the current object
+                $.ajax({
+                    url: "../../system/file_system/versionHistory",
+                    data: {path: targetFile, opr: "new"},
+                    method: "POST",
+                    success: function(data){
+                        if (data.error != undefined){
+                            alert(data.error)
+                        }else{
+                            msgbox(applocale.getString("msgbox/created","New Version Created"));
+                            loadVersionHistory(targetFile);
+                        }
+                    }
+                });
+            }
+
+            function restoreVersion(object){
+                var versionID = $(object).attr("verid");
+                if (confirm(applocale.getString("warning/restore", "Restoring this file will remove all newer version histories of this file. Confirm?"))){
+                    $.ajax({
+                        url: "../../system/file_system/versionHistory",
+                        data: {path: targetFile, opr: "restore", histid: versionID},
+                        method: "POST",
+                        success: function(data){
+                            if (data.error != undefined){
+                                alert(data.error)
+                            }else{
+                                loadVersionHistory(targetFile);
+                                msgbox(applocale.getString("msgbox/restored","Restore Succeed"));
+                            }
+                        }
+                    });
+                }
+            }
+
+            function msgbox(message){
+                $("#msg").text(message);
+                $("#succ").stop().finish().fadeIn("fast").delay(3000).fadeOut("fast");
+            }
+
             function deleteVersion(object){
                 var versionID = $(object).attr("verid");
+                $.ajax({
+                    url: "../../system/file_system/versionHistory",
+                    data: {path: targetFile, opr: "delete", histid: versionID},
+                    method: "POST",
+                    success: function(data){
+                        if (data.error != undefined){
+                            alert(data.error)
+                        }else{
+                            loadVersionHistory(targetFile);
+                        }
+                    }
+                });
+            }
+
+            function deleteAllVersions(object){
+                var versionID = $(object).attr("verid");
+                if (confirm(applocale.getString("warning/deleteall", "This operation will **PERMANENTLY DELETE ALL** version history of this file. Confirm?"))){
+                    $.ajax({
+                        url: "../../system/file_system/versionHistory",
+                        data: {path: targetFile, opr: "deleteAll", histid: versionID},
+                        method: "POST",
+                        success: function(data){
+                            if (data.error != undefined){
+                                alert(data.error)
+                            }else{
+                                msgbox(applocale.getString("msgbox/deletedall","All version history deleted"));
+                                loadVersionHistory(targetFile);
+                            }
+                        }
+                    });
+                }
                 
             }
              

+ 58 - 0
web/SystemAO/locale/file_versions.json

@@ -0,0 +1,58 @@
+{
+    "author": "tobychui",
+    "version": "1.0",
+    "keys": {
+        "zh-tw": {
+            "fwtitle" : "檔案版本管理",
+            "strings":{
+                "title/title": "檔案版本管理",
+                "title/desc":"此檔案的舊版本將會自動於 30 天後刪除。同時間你也可以手動從下方列表中對版本備份進行刪除。",
+                "button/newver":"建立新版本",
+                "button/removeAll":"永久刪除所有舊版本",
+                "message/nofile/title":"沒有檔案",
+                "message/nofile/desc":"使用方法錯誤",
+                "message/nohist/title":"沒有舊版本",
+                "message/nohist/desc":"此檔案並沒有舊版本記錄",
+                "title/download":"下載此版本",
+                "title/restore":"恢復到此版本",
+                "title/delete":"刪除此版本",
+                "msgbox/created":"已建立新檔案版本",
+                "msgbox/restored":"已恢復檔案到指定版本",
+                "msgbox/deletedall":"已刪除所有舊檔案版本",
+                "warning/deleteall":"此動作將會 *永久刪除* 所有此檔案的舊版本備份。確認刪除?",
+                "warning/restore":"恢復到到此檔案版本將會移除所有比此版本更新的版本備份。確認恢復?",
+                "":""
+            },
+            "titles":{
+
+            },
+            "placeholder":{
+
+            }
+        },
+        "zh-hk": {
+            "fwtitle" : "",
+            "strings":{
+
+            },
+            "titles":{
+
+            },
+            "placeholder":{
+
+            }
+        },
+        "zh-cn": {
+            "fwtitle" : "",
+            "strings":{
+
+            },
+            "titles":{
+
+            },
+            "placeholder":{
+
+            }
+        }
+    }
+}