浏览代码

Added WIP fs abstraciton support (NOT WORKING, DO NOT BUILD!!!!!)

Toby Chui 3 年之前
父节点
当前提交
f4391c43b1

文件差异内容过多而无法显示
+ 342 - 225
file_system.go


+ 2 - 1
go.mod

@@ -17,7 +17,7 @@ require (
 	github.com/gabriel-vasile/mimetype v1.4.0
 	github.com/go-git/go-git/v5 v5.4.2
 	github.com/go-ldap/ldap v3.0.3+incompatible
-	github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
+	github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0
 	github.com/golang/snappy v0.0.4 // indirect
 	github.com/gopherjs/gopherjs v1.17.2 // indirect
 	github.com/gorilla/sessions v1.2.1
@@ -37,6 +37,7 @@ require (
 	github.com/satori/go.uuid v1.2.0
 	github.com/sergi/go-diff v1.2.0 // indirect
 	github.com/spf13/afero v1.8.2
+	github.com/studio-b12/gowebdav v0.0.0-20220128162035-c7b1ff8a5e62
 	github.com/tidwall/pretty v1.2.0
 	github.com/ulikunitz/xz v0.5.10 // indirect
 	github.com/valyala/fasttemplate v1.2.1

+ 2 - 2
go.sum

@@ -412,6 +412,8 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
 github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/studio-b12/gowebdav v0.0.0-20220128162035-c7b1ff8a5e62 h1:b2nJXyPCa9HY7giGM+kYcnQ71m14JnGdQabMPmyt++8=
+github.com/studio-b12/gowebdav v0.0.0-20220128162035-c7b1ff8a5e62/go.mod h1:bHA7t77X/QFExdeAnDzK6vKM34kEZAcE1OX4MfiwjkE=
 github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
 github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
 github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
@@ -484,8 +486,6 @@ golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMk
 golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
 golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
 golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
-golang.org/x/image v0.0.0-20220413100746-70e8d0d3baa9 h1:LRtI4W37N+KFebI/qV0OFiLUv4GLOWeEW5hn/KEJvxE=
-golang.org/x/image v0.0.0-20220413100746-70e8d0d3baa9/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
 golang.org/x/image v0.0.0-20220617043117-41969df76e82 h1:KpZB5pUSBvrHltNEdK/tw0xlPeD13M6M6aGP32gKqiw=
 golang.org/x/image v0.0.0-20220617043117-41969df76e82/go.mod h1:doUCurBvlfPMKfmIpRIywoHmhN3VyhnoFDbvIEWF4hY=
 golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=

+ 59 - 20
mediaServer.go

@@ -2,13 +2,16 @@ package main
 
 import (
 	"errors"
+	"io"
 	"log"
 	"net/http"
 	"net/url"
 	"path/filepath"
+	"strconv"
 	"strings"
 
 	"imuslab.com/arozos/mod/common"
+	"imuslab.com/arozos/mod/filesystem"
 	fs "imuslab.com/arozos/mod/filesystem"
 	"imuslab.com/arozos/mod/network/gzipmiddleware"
 )
@@ -40,38 +43,43 @@ func mediaServer_init() {
 }
 
 //This function validate the incoming media request and return the real path for the targed file
-func media_server_validateSourceFile(w http.ResponseWriter, r *http.Request) (string, error) {
+func media_server_validateSourceFile(w http.ResponseWriter, r *http.Request) (*filesystem.FileSystemHandler, string, error) {
 	username, err := authAgent.GetUserName(w, r)
 	if err != nil {
-		return "", errors.New("User not logged in")
+		return nil, "", errors.New("User not logged in")
 	}
 
 	userinfo, _ := userHandler.GetUserInfoFromUsername(username)
 
 	//Validate url valid
 	if strings.Count(r.URL.String(), "?") > 1 {
-		return "", errors.New("Invalid paramters. Multiple ? found")
+		return nil, "", errors.New("Invalid paramters. Multiple ? found")
 	}
 
 	targetfile, _ := common.Mv(r, "file", false)
 	targetfile, err = url.QueryUnescape(targetfile)
 	if err != nil {
-		return "", err
+		return nil, "", err
 	}
 	if targetfile == "" {
-		return "", errors.New("Missing paramter 'file'")
+		return nil, "", errors.New("Missing paramter 'file'")
 	}
 
 	//Translate the virtual directory to realpath
-	realFilepath, err := userinfo.VirtualPathToRealPath(targetfile)
-	if fs.FileExists(realFilepath) && fs.IsDir(realFilepath) {
-		return "", errors.New("Given path is not a file.")
+	fsh, subpath, err := GetFSHandlerSubpathFromVpath(targetfile)
+	if err != nil {
+		return nil, "", errors.New("Unable to load from target file system")
+	}
+	fshAbs := fsh.FileSystemAbstraction
+	realFilepath, err := fshAbs.VirtualPathToRealPath(subpath, userinfo.Username)
+	if fshAbs.FileExists(realFilepath) && fshAbs.IsDir(realFilepath) {
+		return nil, "", errors.New("Given path is not a file")
 	}
 	if err != nil {
-		return "", errors.New("Unable to translate the given filepath")
+		return nil, "", errors.New("Unable to translate the given filepath")
 	}
 
-	if !fs.FileExists(realFilepath) {
+	if !fshAbs.FileExists(realFilepath) {
 		//Sometime if url is not URL encoded, this error might be shown as well
 
 		//Try to use manual segmentation
@@ -89,31 +97,38 @@ func media_server_validateSourceFile(w http.ResponseWriter, r *http.Request) (st
 		}
 		urlInfo := strings.Split(originalURL, "file=")
 		possibleVirtualFilePath := urlInfo[len(urlInfo)-1]
-		possibleRealpath, err := userinfo.VirtualPathToRealPath(possibleVirtualFilePath)
+		possibleRealpath, err := fshAbs.VirtualPathToRealPath(possibleVirtualFilePath, userinfo.Username)
 		if err != nil {
 			log.Println("Error when trying to serve file in compatibility mode", err.Error())
-			return "", errors.New("Error when trying to serve file in compatibility mode")
+			return nil, "", errors.New("Error when trying to serve file in compatibility mode")
 		}
-		if fs.FileExists(possibleRealpath) {
+		if fshAbs.FileExists(possibleRealpath) {
 			realFilepath = possibleRealpath
 			log.Println("[Media Server] Serving file " + filepath.Base(possibleRealpath) + " in compatibility mode. Do not to use '&' or '+' sign in filename! ")
-			return realFilepath, nil
+			return fsh, realFilepath, nil
 		} else {
-			return "", errors.New("File not exists")
+			return nil, "", errors.New("File not exists")
 		}
 	}
 
-	return realFilepath, nil
+	return fsh, realFilepath, nil
 }
 
 func serveMediaMime(w http.ResponseWriter, r *http.Request) {
-	realFilepath, err := media_server_validateSourceFile(w, r)
+	targetFsh, realFilepath, err := media_server_validateSourceFile(w, r)
 	if err != nil {
 		common.SendErrorResponse(w, err.Error())
 		return
 	}
+	targetFshAbs := targetFsh.FileSystemAbstraction
+	if targetFsh.RequireBuffer {
+		//File is not on local. Guess its mime by extension
+		common.SendTextResponse(w, "application/"+filepath.Ext(realFilepath)[1:])
+		return
+	}
+
 	mime := "text/directory"
-	if !fs.IsDir(realFilepath) {
+	if !targetFshAbs.IsDir(realFilepath) {
 		m, _, err := fs.GetMime(realFilepath)
 		if err != nil {
 			mime = ""
@@ -126,12 +141,14 @@ func serveMediaMime(w http.ResponseWriter, r *http.Request) {
 
 func serverMedia(w http.ResponseWriter, r *http.Request) {
 	//Serve normal media files
-	realFilepath, err := media_server_validateSourceFile(w, r)
+	targetFsh, realFilepath, err := media_server_validateSourceFile(w, r)
 	if err != nil {
 		common.SendErrorResponse(w, err.Error())
 		return
 	}
 
+	targetFshAbs := targetFsh.FileSystemAbstraction
+
 	//Check if downloadMode
 	downloadMode := false
 	dw, _ := common.Mv(r, "download", false)
@@ -167,9 +184,31 @@ func serverMedia(w http.ResponseWriter, r *http.Request) {
 
 		w.Header().Set("Content-Disposition", "attachment; filename=\""+filename+"\"")
 		w.Header().Set("Content-Type", r.Header.Get("Content-Type"))
+		if targetFsh.RequireBuffer {
+			//Stream it directly from remote
+			w.Header().Set("Content-Length", strconv.Itoa(int(targetFshAbs.GetFileSize(realFilepath))))
+			remoteStream, err := targetFshAbs.ReadStream(realFilepath)
+			if err != nil {
+				common.SendErrorResponse(w, err.Error())
+				return
+			}
+			io.Copy(w, remoteStream)
+			return
+		} else {
+			http.ServeFile(w, r, escapedRealFilepath)
+		}
 
-		http.ServeFile(w, r, escapedRealFilepath)
 	} else {
+		if targetFsh.RequireBuffer {
+			w.Header().Set("Content-Length", strconv.Itoa(int(targetFshAbs.GetFileSize(realFilepath))))
+			remoteStream, err := targetFshAbs.ReadStream(realFilepath)
+			if err != nil {
+				common.SendErrorResponse(w, err.Error())
+				return
+			}
+			io.Copy(w, remoteStream)
+			return
+		}
 		http.ServeFile(w, r, realFilepath)
 	}
 

+ 1 - 1
mod/agi/agi.file.go

@@ -289,7 +289,7 @@ func (g *Gateway) injectFileLibFunctions(vm *otto.Otto, u *user.User) {
 			rootDirs := []string{}
 			fileHandlers := u.GetAllFileSystemHandler()
 			for _, fsh := range fileHandlers {
-				if fsh.Hierarchy == "backup" || fsh.Filesystem == "virtual" {
+				if fsh.Hierarchy == "backup" || fsh.IsVirtual() {
 
 				} else {
 					rootDirs = append(rootDirs, fsh.UUID+":/")

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

@@ -448,3 +448,12 @@ func (m *Manager) GetTaskByBackupDiskID(backupDiskID string) (*BackupTask, error
 	}
 	return targetTask, nil
 }
+
+//Resolver for Vroots
+func (t BackupTask) ResolveVrootPath(string, string) (string, error) {
+	return "", errors.New("Unable to resolve in backup file system")
+}
+
+func (t BackupTask) ResolveRealPath(string, string) (string, error) {
+	return "", errors.New("Unable to resolve in backup file system")
+}

+ 112 - 0
mod/filesystem/emptyFilesystemAbstraction.go

@@ -0,0 +1,112 @@
+package filesystem
+
+import (
+	"errors"
+	"io"
+	"os"
+	"path/filepath"
+	"time"
+)
+
+/*
+	filesystemAbstraction.go
+
+	This file contains all the abstraction funtion of a local file system.
+
+*/
+
+type EmptyFileSystemAbstraction struct {
+}
+
+func NewEmptyFileSystemAbstraction() EmptyFileSystemAbstraction {
+	return EmptyFileSystemAbstraction{}
+}
+
+func (l EmptyFileSystemAbstraction) Chmod(filename string, mode os.FileMode) error {
+	return nil
+}
+func (l EmptyFileSystemAbstraction) Chown(filename string, uid int, gid int) error {
+	return nil
+}
+func (l EmptyFileSystemAbstraction) Chtimes(filename string, atime time.Time, mtime time.Time) error {
+	return nil
+}
+func (l EmptyFileSystemAbstraction) Create(filename string) (*os.File, error) {
+	return nil, nil
+}
+func (l EmptyFileSystemAbstraction) Mkdir(filename string, mode os.FileMode) error {
+	return nil
+}
+func (l EmptyFileSystemAbstraction) MkdirAll(filename string, mode os.FileMode) error {
+	return nil
+}
+func (l EmptyFileSystemAbstraction) Name() string {
+	return ""
+}
+func (l EmptyFileSystemAbstraction) Open(filename string) (*os.File, error) {
+	return nil, nil
+}
+func (l EmptyFileSystemAbstraction) OpenFile(filename string, flag int, perm os.FileMode) (*os.File, error) {
+	return nil, nil
+}
+func (l EmptyFileSystemAbstraction) Remove(filename string) error {
+	return nil
+}
+func (l EmptyFileSystemAbstraction) RemoveAll(path string) error {
+	return nil
+}
+func (l EmptyFileSystemAbstraction) Rename(oldname, newname string) error {
+	return nil
+}
+func (l EmptyFileSystemAbstraction) Stat(filename string) (os.FileInfo, error) {
+	return nil, nil
+}
+
+/*
+	Abstraction Utilities
+*/
+
+func (l EmptyFileSystemAbstraction) VirtualPathToRealPath(subpath string, username string) (string, error) {
+	return "", errors.New("empty filesystem abstraction")
+}
+
+func (l EmptyFileSystemAbstraction) RealPathToVirtualPath(fullpath string, username string) (string, error) {
+	return "", errors.New("empty filesystem abstraction")
+}
+
+func (l EmptyFileSystemAbstraction) FileExists(realpath string) bool {
+	return false
+}
+
+func (l EmptyFileSystemAbstraction) IsDir(realpath string) bool {
+	return false
+}
+
+func (l EmptyFileSystemAbstraction) Glob(realpathWildcard string) ([]string, error) {
+	return []string{}, nil
+}
+
+func (l EmptyFileSystemAbstraction) GetFileSize(realpath string) int64 {
+	return 0
+}
+
+func (l EmptyFileSystemAbstraction) GetModTime(realpath string) (int64, error) {
+	return 0, errors.New("empty filesystem abstraction")
+}
+
+func (l EmptyFileSystemAbstraction) WriteFile(filename string, content []byte, mode os.FileMode) error {
+	return nil
+}
+func (l EmptyFileSystemAbstraction) ReadFile(filename string) ([]byte, error) {
+	return []byte(""), errors.New("empty filesystem abstraction")
+}
+func (l EmptyFileSystemAbstraction) WriteStream(filename string, stream io.Reader, mode os.FileMode) error {
+	return errors.New("operation not supported on dummy file system")
+}
+func (l EmptyFileSystemAbstraction) ReadStream(filename string) (io.ReadCloser, error) {
+	return nil, errors.New("operation not supported on dummy file system")
+}
+
+func (l EmptyFileSystemAbstraction) Walk(root string, walkFn filepath.WalkFunc) error {
+	return errors.New("empty filesystem abstraction")
+}

+ 143 - 48
mod/filesystem/filesystem.go

@@ -12,6 +12,7 @@ package filesystem
 
 import (
 	"errors"
+	"io"
 	"log"
 	"os"
 	"path/filepath"
@@ -20,6 +21,7 @@ import (
 
 	db "imuslab.com/arozos/mod/database"
 	"imuslab.com/arozos/mod/disk/hybridBackup"
+	webdavclient "imuslab.com/arozos/mod/filesystem/webdavClient"
 )
 
 //Options for creating new file system handler
@@ -44,19 +46,52 @@ type FileSystemOpeningOptions struct{
 */
 type HierarchySpecificConfig interface{}
 
+type FileSystemAbstraction interface {
+	//Fundemental Functions
+	Chmod(string, os.FileMode) error
+	Chown(string, int, int) error
+	Chtimes(string, time.Time, time.Time) error
+	Create(string) (*os.File, error)
+	Mkdir(string, os.FileMode) error
+	MkdirAll(string, os.FileMode) error
+	Name() string
+	Open(string) (*os.File, error)
+	OpenFile(string, int, os.FileMode) (*os.File, error)
+	Remove(string) error
+	RemoveAll(string) error
+	Rename(string, string) error
+	Stat(string) (os.FileInfo, error)
+
+	//Utils Functions
+	VirtualPathToRealPath(string, string) (string, error)
+	RealPathToVirtualPath(string, string) (string, error)
+	FileExists(string) bool
+	IsDir(string) bool
+	Glob(string) ([]string, error)
+	GetFileSize(string) int64
+	GetModTime(string) (int64, error)
+	WriteFile(string, []byte, os.FileMode) error
+	ReadFile(string) ([]byte, error)
+	WriteStream(string, io.Reader, os.FileMode) error
+	ReadStream(string) (io.ReadCloser, error)
+	Walk(string, filepath.WalkFunc) error
+}
+
 //System Handler for returing
 type FileSystemHandler struct {
-	Name               string
-	UUID               string
-	Path               string
-	Hierarchy          string
-	HierarchyConfig    HierarchySpecificConfig
-	ReadOnly           bool
-	Parentuid          string
-	InitiationTime     int64
-	FilesystemDatabase *db.Database
-	Filesystem         string
-	Closed             bool
+	Name                  string
+	UUID                  string
+	Path                  string
+	Hierarchy             string
+	HierarchyConfig       HierarchySpecificConfig
+	ReadOnly              bool
+	RequireBuffer         bool //Set this to true if the fsh do not provide file header functions like Open() or Create(), require WriteStream() and ReadStream()
+	Parentuid             string
+	InitiationTime        int64
+	FilesystemDatabase    *db.Database
+	FileSystemAbstraction FileSystemAbstraction
+	Filesystem            string
+	Closed                bool
 }
 
 //Create a list of file system handler from the given json content
@@ -99,16 +134,26 @@ func NewFileSystemHandler(option FileSystemOption) (*FileSystemHandler, error) {
 		}
 
 		//Handle Hierarchy branching
-		var hierarchySpecificConfig interface{} = nil
-
 		if option.Hierarchy == "user" {
 			//Create user hierarchy for this virtual device
 			os.MkdirAll(filepath.ToSlash(filepath.Clean(option.Path))+"/users", 0755)
 		}
 
+		//Create the fsdb for this handler
+		var fsdb *db.Database = nil
+		dbp, err := db.NewDatabase(filepath.ToSlash(filepath.Join(filepath.Clean(option.Path), "aofs.db")), false)
+		if err != nil {
+			if option.Access != "readonly" {
+				log.Println("[Filesystem] Invalid config: Trying to mount a read only path as read-write mount point. Changing " + option.Name + " mount point to READONLY.")
+				option.Access = "readonly"
+			}
+		} else {
+			fsdb = dbp
+		}
+		rootpath := filepath.ToSlash(filepath.Clean(option.Path)) + "/"
 		if option.Hierarchy == "backup" {
 			//Backup disk. Create an Hierarchy Config for this drive
-			hierarchySpecificConfig = hybridBackup.BackupTask{
+			hsConfig := hybridBackup.BackupTask{
 				CycleCounter:      0,
 				LastCycleTime:     0,
 				DiskUID:           option.Uuid,
@@ -118,51 +163,80 @@ func NewFileSystemHandler(option FileSystemOption) (*FileSystemHandler, error) {
 				DeleteFileMarkers: map[string]int64{},
 				PanicStopped:      false,
 			}
-
+			return &FileSystemHandler{
+				Name:                  option.Name,
+				UUID:                  option.Uuid,
+				Path:                  rootpath,
+				ReadOnly:              option.Access == "readonly",
+				RequireBuffer:         false,
+				Parentuid:             option.Parentuid,
+				Hierarchy:             option.Hierarchy,
+				HierarchyConfig:       hsConfig,
+				InitiationTime:        time.Now().Unix(),
+				FilesystemDatabase:    fsdb,
+				FileSystemAbstraction: NewLocalFileSystemAbstraction(option.Uuid, rootpath, option.Hierarchy, option.Access == "readonly"),
+				Filesystem:            fstype,
+				Closed:                false,
+			}, nil
+		} else {
+			return &FileSystemHandler{
+				Name:                  option.Name,
+				UUID:                  option.Uuid,
+				Path:                  filepath.ToSlash(filepath.Clean(option.Path)) + "/",
+				ReadOnly:              option.Access == "readonly",
+				RequireBuffer:         false,
+				Parentuid:             option.Parentuid,
+				Hierarchy:             option.Hierarchy,
+				HierarchyConfig:       DefaultEmptyHierarchySpecificConfig,
+				InitiationTime:        time.Now().Unix(),
+				FilesystemDatabase:    fsdb,
+				FileSystemAbstraction: NewLocalFileSystemAbstraction(option.Uuid, rootpath, option.Hierarchy, option.Access == "readonly"),
+				Filesystem:            fstype,
+				Closed:                false,
+			}, nil
 		}
 
-		//Create the fsdb for this handler
-		var fsdb *db.Database = nil
+	} else if fstype == "webdav" {
+		//WebDAV. Create an object and mount it
+		root := option.Path
+		user := option.Username
+		password := option.Password
 
-		dbp, err := db.NewDatabase(filepath.ToSlash(filepath.Join(filepath.Clean(option.Path), "aofs.db")), false)
-		if err != nil {
-			if option.Access != "readonly" {
-				log.Println("[Filesystem] Invalid config: Trying to mount a read only path as read-write mount point. Changing " + option.Name + " mount point to READONLY.")
-				option.Access = "readonly"
-			}
-		} else {
-			fsdb = dbp
-		}
+		webdavfs, _ := webdavclient.NewWebDAVMount(option.Uuid, option.Hierarchy, root, user, password, "./tmp/webdavBuff")
 
 		return &FileSystemHandler{
-			Name:               option.Name,
-			UUID:               option.Uuid,
-			Path:               filepath.ToSlash(filepath.Clean(option.Path)) + "/",
-			ReadOnly:           option.Access == "readonly",
-			Parentuid:          option.Parentuid,
-			Hierarchy:          option.Hierarchy,
-			HierarchyConfig:    hierarchySpecificConfig,
-			InitiationTime:     time.Now().Unix(),
-			FilesystemDatabase: fsdb,
-			Filesystem:         fstype,
-			Closed:             false,
+			Name:                  option.Name,
+			UUID:                  option.Uuid,
+			Path:                  "",
+			ReadOnly:              option.Access == "readonly",
+			RequireBuffer:         true,
+			Parentuid:             option.Parentuid,
+			Hierarchy:             option.Hierarchy,
+			HierarchyConfig:       nil,
+			InitiationTime:        time.Now().Unix(),
+			FilesystemDatabase:    nil,
+			FileSystemAbstraction: webdavfs,
+			Filesystem:            fstype,
+			Closed:                false,
 		}, nil
 	} else if option.Filesystem == "virtual" {
 		//Virtual filesystem, use custom mapping logic to handle file access
 		if option.Hierarchy == "share" {
 			//Emulated share virtual file system. Use Share Manager structure
 			return &FileSystemHandler{
-				Name:               option.Name,
-				UUID:               option.Uuid,
-				Path:               "",
-				ReadOnly:           option.Access == "readonly",
-				Parentuid:          option.Parentuid,
-				Hierarchy:          option.Hierarchy,
-				HierarchyConfig:    nil,
-				InitiationTime:     time.Now().Unix(),
-				FilesystemDatabase: nil,
-				Filesystem:         fstype,
-				Closed:             false,
+				Name:                  option.Name,
+				UUID:                  option.Uuid,
+				Path:                  "",
+				ReadOnly:              option.Access == "readonly",
+				RequireBuffer:         false,
+				Parentuid:             option.Parentuid,
+				Hierarchy:             option.Hierarchy,
+				HierarchyConfig:       nil,
+				InitiationTime:        time.Now().Unix(),
+				FilesystemDatabase:    nil,
+				FileSystemAbstraction: NewEmptyFileSystemAbstraction(),
+				Filesystem:            fstype,
+				Closed:                false,
 			}, nil
 		}
 	}
@@ -170,6 +244,27 @@ func NewFileSystemHandler(option FileSystemOption) (*FileSystemHandler, error) {
 	return nil, errors.New("Not supported file system: " + fstype)
 }
 
+//Check if a fsh is virtual (e.g. Network or fs Abstractions that cannot be listed with normal fs API)
+func (fsh *FileSystemHandler) IsVirtual() bool {
+	if fsh.Hierarchy == "virtual" || fsh.Filesystem == "webdav" {
+		//Check if the config return placeholder
+		c, ok := fsh.HierarchyConfig.(EmptyHierarchySpecificConfig)
+		if ok && c.HierarchyType == "placeholder" {
+			//Real file system.
+			return false
+		}
+
+		//Do more checking here if needed
+		return true
+	}
+	return false
+}
+
+/*
+	File Record Related Functions
+	fsh database that keep track of which files is owned by whom
+*/
+
 //Create a file ownership record
 func (fsh *FileSystemHandler) CreateFileRecord(realpath string, owner string) error {
 	if fsh.FilesystemDatabase == nil {

+ 227 - 0
mod/filesystem/localFilesystemAbstraction.go

@@ -0,0 +1,227 @@
+package filesystem
+
+import (
+	"errors"
+	"io"
+	"log"
+	"os"
+	"path/filepath"
+	"strings"
+	"time"
+
+	"imuslab.com/arozos/mod/common"
+)
+
+/*
+	filesystemAbstraction.go
+
+	This file contains all the abstraction funtion of a local file system.
+
+*/
+
+type LocalFileSystemAbstraction struct {
+	UUID      string
+	Rootpath  string
+	Hierarchy string
+	ReadOnly  bool
+}
+
+func NewLocalFileSystemAbstraction(uuid, root, hierarchy string, readonly bool) LocalFileSystemAbstraction {
+	return LocalFileSystemAbstraction{
+		UUID:      uuid,
+		Rootpath:  root,
+		Hierarchy: hierarchy,
+		ReadOnly:  readonly,
+	}
+}
+
+func (l LocalFileSystemAbstraction) Chmod(filename string, mode os.FileMode) error {
+	return os.Chmod(filename, mode)
+}
+func (l LocalFileSystemAbstraction) Chown(filename string, uid int, gid int) error {
+	return os.Chown(filename, uid, gid)
+}
+func (l LocalFileSystemAbstraction) Chtimes(filename string, atime time.Time, mtime time.Time) error {
+	return os.Chtimes(filename, atime, mtime)
+}
+func (l LocalFileSystemAbstraction) Create(filename string) (*os.File, error) {
+	return os.Create(filename)
+}
+func (l LocalFileSystemAbstraction) Mkdir(filename string, mode os.FileMode) error {
+	return os.Mkdir(filename, mode)
+}
+func (l LocalFileSystemAbstraction) MkdirAll(filename string, mode os.FileMode) error {
+	return os.MkdirAll(filename, mode)
+}
+func (l LocalFileSystemAbstraction) Name() string {
+	return ""
+}
+func (l LocalFileSystemAbstraction) Open(filename string) (*os.File, error) {
+	return os.Open(filename)
+}
+func (l LocalFileSystemAbstraction) OpenFile(filename string, flag int, perm os.FileMode) (*os.File, error) {
+	return os.OpenFile(filename, flag, perm)
+}
+func (l LocalFileSystemAbstraction) Remove(filename string) error {
+	return os.Remove(filename)
+}
+func (l LocalFileSystemAbstraction) RemoveAll(path string) error {
+	return os.RemoveAll(path)
+}
+func (l LocalFileSystemAbstraction) Rename(oldname, newname string) error {
+	return os.Rename(oldname, newname)
+}
+func (l LocalFileSystemAbstraction) Stat(filename string) (os.FileInfo, error) {
+	return os.Stat(filename)
+}
+
+/*
+	Abstraction Utilities
+*/
+
+func (l LocalFileSystemAbstraction) VirtualPathToRealPath(subpath string, username string) (string, error) {
+	if strings.HasPrefix(subpath, l.UUID+":/") {
+		//This is full virtual path. Trim the uuid and correct the subpath
+		subpath = subpath[len(l.UUID+":/"):]
+	}
+
+	if l.Hierarchy == "user" {
+		return filepath.ToSlash(filepath.Join(l.Rootpath, "users", username, subpath)), nil
+	} else if l.Hierarchy == "public" {
+		return filepath.ToSlash(filepath.Join(l.Rootpath, subpath)), nil
+	}
+	return "", errors.New("unsupported filesystem hierarchy")
+}
+
+func (l LocalFileSystemAbstraction) RealPathToVirtualPath(fullpath string, username string) (string, error) {
+	thisStorageRootAbs, err := filepath.Abs(l.Rootpath)
+	if err != nil {
+		//Fail to abs this path. Maybe this is a emulated file system?
+		thisStorageRootAbs = l.Rootpath
+	}
+	thisStorageRootAbs = filepath.ToSlash(filepath.Clean(thisStorageRootAbs))
+
+	subPath := ""
+	if len(fullpath) > len(l.Rootpath) && filepath.ToSlash(fullpath[:len(l.Rootpath)]) == filepath.ToSlash(l.Rootpath) {
+		//This realpath is in contained inside this storage root
+		subtractionPath := l.Rootpath
+		if l.Hierarchy == "user" {
+			//Check if this file is belongs to this user
+			startOffset := len(filepath.Clean(l.Rootpath) + "/users/")
+			if len(fullpath) < startOffset+len(username) {
+				//This file is not owned by this user
+				return "", errors.New("File not owned by this user")
+			} else {
+				userNameMatch := fullpath[startOffset : startOffset+len(username)]
+				if userNameMatch != username {
+					//This file is not owned by this user
+					return "", errors.New("File not owned by this user")
+				}
+			}
+
+			//Generate subtraction path
+			subtractionPath = filepath.ToSlash(filepath.Clean(filepath.Join(l.Rootpath, "users", username)))
+		}
+
+		if len(subtractionPath) < len(fullpath) {
+			subPath = fullpath[len(subtractionPath):]
+		}
+
+	} else if len(fullpath) > len(thisStorageRootAbs) && filepath.ToSlash(fullpath[:len(thisStorageRootAbs)]) == filepath.ToSlash(thisStorageRootAbs) {
+		//The realpath contains the absolute path of this storage root
+		subtractionPath := thisStorageRootAbs
+		if l.Hierarchy == "user" {
+			subtractionPath = thisStorageRootAbs + "/users/" + username + "/"
+		}
+
+		if len(subtractionPath) < len(fullpath) {
+			subPath = fullpath[len(subtractionPath):]
+		}
+	} else if filepath.ToSlash(fullpath) == filepath.ToSlash(l.Rootpath) {
+		//Storage Root's root
+		subPath = ""
+	}
+
+	if len(subPath) > 1 && subPath[:1] == "/" {
+		subPath = subPath[1:]
+	}
+
+	return l.UUID + ":" + filepath.ToSlash(subPath), nil
+}
+
+func (l LocalFileSystemAbstraction) FileExists(realpath string) bool {
+	return common.FileExists(realpath)
+}
+
+func (l LocalFileSystemAbstraction) IsDir(realpath string) bool {
+	if !l.FileExists(realpath) {
+		return false
+	}
+	fi, err := l.Stat(realpath)
+	if err != nil {
+		log.Fatal(err)
+		return false
+	}
+	switch mode := fi.Mode(); {
+	case mode.IsDir():
+		return true
+	case mode.IsRegular():
+		return false
+	}
+	return false
+}
+
+func (l LocalFileSystemAbstraction) Glob(realpathWildcard string) ([]string, error) {
+	return filepath.Glob(realpathWildcard)
+}
+
+func (l LocalFileSystemAbstraction) GetFileSize(realpath string) int64 {
+	fi, err := os.Stat(realpath)
+	if err != nil {
+		return 0
+	}
+	// get the size
+	return fi.Size()
+}
+
+func (l LocalFileSystemAbstraction) GetModTime(realpath string) (int64, error) {
+	f, err := os.Open(realpath)
+	if err != nil {
+		return -1, err
+	}
+	statinfo, err := f.Stat()
+	if err != nil {
+		return -1, err
+	}
+	f.Close()
+	return statinfo.ModTime().Unix(), nil
+}
+
+func (l LocalFileSystemAbstraction) WriteFile(filename string, content []byte, mode os.FileMode) error {
+	return os.WriteFile(filename, content, mode)
+}
+func (l LocalFileSystemAbstraction) ReadFile(filename string) ([]byte, error) {
+	return os.ReadFile(filename)
+}
+func (l LocalFileSystemAbstraction) WriteStream(filename string, stream io.Reader, mode os.FileMode) error {
+	f, err := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY, mode)
+	if err != nil {
+		return err
+	}
+
+	_, err = io.Copy(f, stream)
+	defer f.Close()
+	return err
+}
+func (l LocalFileSystemAbstraction) ReadStream(filename string) (io.ReadCloser, error) {
+	f, err := os.OpenFile(filename, os.O_RDONLY, 0644)
+	if err != nil {
+		return nil, err
+	}
+	defer f.Close()
+	return f, nil
+}
+
+func (l LocalFileSystemAbstraction) Walk(root string, walkFn filepath.WalkFunc) error {
+	return filepath.Walk(root, walkFn)
+}

+ 19 - 0
mod/filesystem/static.go

@@ -62,6 +62,25 @@ type FileProperties struct {
 	IsDirectory    bool
 }
 
+/*
+	HierarchySpecificConfig Template
+*/
+
+type EmptyHierarchySpecificConfig struct {
+	HierarchyType string
+}
+
+func (e EmptyHierarchySpecificConfig) ResolveVrootPath(string, string) (string, error) {
+	return "", nil
+}
+func (e EmptyHierarchySpecificConfig) ResolveRealPath(string, string) (string, error) {
+	return "", nil
+}
+
+var DefaultEmptyHierarchySpecificConfig = EmptyHierarchySpecificConfig{
+	HierarchyType: "placeholder",
+}
+
 //Check if the two file system are identical.
 func MatchingFileSystem(fsa *FileSystemHandler, fsb *FileSystemHandler) bool {
 	return fsa.Filesystem == fsb.Filesystem

+ 53 - 0
mod/filesystem/webdavClient/webdavFsAbstraction.go.disabled

@@ -0,0 +1,53 @@
+package webdavclient
+
+import (
+	"os"
+	"time"
+)
+
+type WebDAVFileSystemAbstraction struct {
+}
+
+func NewWebDAVFileSystemAbstraction() WebDAVFileSystemAbstraction {
+	return WebDAVFileSystemAbstraction{}
+}
+
+func (l WebDAVFileSystemAbstraction) Chmod(filename string, mode os.FileMode) error {
+	return os.Chmod(filename, mode)
+}
+func (l WebDAVFileSystemAbstraction) Chown(filename string, uid int, gid int) error {
+	return os.Chown(filename, uid, gid)
+}
+func (l WebDAVFileSystemAbstraction) Chtimes(filename string, atime time.Time, mtime time.Time) error {
+	return os.Chtimes(filename, atime, mtime)
+}
+func (l WebDAVFileSystemAbstraction) Create(filename string) (*os.File, error) {
+	return os.Create(filename)
+}
+func (l WebDAVFileSystemAbstraction) Mkdir(filename string, mode os.FileMode) error {
+	return os.Mkdir(filename, mode)
+}
+func (l WebDAVFileSystemAbstraction) MkdirAll(filename string, mode os.FileMode) error {
+	return os.MkdirAll(filename, mode)
+}
+func (l WebDAVFileSystemAbstraction) Name() string {
+	return ""
+}
+func (l WebDAVFileSystemAbstraction) Open(filename string) (*os.File, error) {
+	return os.Open(filename)
+}
+func (l WebDAVFileSystemAbstraction) OpenFile(filename string, flag int, perm os.FileMode) (*os.File, error) {
+	return os.OpenFile(filename, flag, perm)
+}
+func (l WebDAVFileSystemAbstraction) Remove(filename string) error {
+	return os.Remove(filename)
+}
+func (l WebDAVFileSystemAbstraction) RemoveAll(path string) error {
+	return os.RemoveAll(path)
+}
+func (l WebDAVFileSystemAbstraction) Rename(oldname, newname string) error {
+	return os.Rename(oldname, newname)
+}
+func (l WebDAVFileSystemAbstraction) Stat(filename string) (os.FileInfo, error) {
+	return os.Stat(filename)
+}

+ 227 - 0
mod/filesystem/webdavClient/webdavclient.go

@@ -0,0 +1,227 @@
+package webdavclient
+
+import (
+	"errors"
+	"io"
+	"log"
+	"os"
+	"path/filepath"
+	"regexp"
+	"strings"
+	"time"
+
+	"github.com/studio-b12/gowebdav"
+)
+
+/*
+	WebDAV Client
+
+	This script is design as a wrapper of the studio-b12/gowebdav module
+	that allow access to webdav network drive in ArozOS and allow arozos
+	cross-mounting each others
+
+*/
+
+type WebDAVFileSystem struct {
+	UUID      string
+	Hierarchy string
+	root      string
+	user      string
+	tmp       string
+	c         *gowebdav.Client
+}
+
+func NewWebDAVMount(UUID string, Hierarchy string, root string, user string, password string, tmpBuff string) (*WebDAVFileSystem, error) {
+	//Connect to webdav server
+	c := gowebdav.NewClient(root, user, password)
+	err := c.Connect()
+	if err != nil {
+		log.Println("[WebDAV FS] Unable to connect to remote: ", err.Error())
+		return nil, err
+	} else {
+		log.Println("[WebDAV FS] Connected to remote: " + root)
+	}
+
+	//Create tmp buff folder if not exists
+	os.MkdirAll(tmpBuff, 0775)
+
+	return &WebDAVFileSystem{
+		UUID:      UUID,
+		Hierarchy: Hierarchy,
+		c:         c,
+		root:      root,
+		user:      user,
+		tmp:       tmpBuff,
+	}, nil
+}
+
+func (e WebDAVFileSystem) Chmod(filename string, mode os.FileMode) error {
+	return errors.New("filesystem type not supported")
+}
+func (e WebDAVFileSystem) Chown(filename string, uid int, gid int) error {
+	return errors.New("filesystem type not supported")
+}
+func (e WebDAVFileSystem) Chtimes(filename string, atime time.Time, mtime time.Time) error {
+	return errors.New("filesystem type not supported")
+}
+func (e WebDAVFileSystem) Create(filename string) (*os.File, error) {
+	return nil, errors.New("filesystem type not supported")
+}
+func (e WebDAVFileSystem) Mkdir(filename string, mode os.FileMode) error {
+	filename = filepath.ToSlash(filename)
+	return e.c.Mkdir(filename, mode)
+}
+func (e WebDAVFileSystem) MkdirAll(filename string, mode os.FileMode) error {
+	filename = filepath.ToSlash(filename)
+	return e.c.MkdirAll(filename, mode)
+}
+func (e WebDAVFileSystem) Name() string {
+	return ""
+}
+func (e WebDAVFileSystem) Open(filename string) (*os.File, error) {
+	return nil, errors.New("filesystem type not supported")
+}
+func (e WebDAVFileSystem) OpenFile(filename string, flag int, perm os.FileMode) (*os.File, error) {
+	return nil, errors.New("filesystem type not supported")
+}
+func (e WebDAVFileSystem) Remove(filename string) error {
+	filename = filepath.ToSlash(filename)
+	return e.c.Remove(filename)
+}
+func (e WebDAVFileSystem) RemoveAll(filename string) error {
+	filename = filepath.ToSlash(filename)
+	return e.c.RemoveAll(filename)
+}
+func (e WebDAVFileSystem) Rename(oldname, newname string) error {
+	oldname = filepath.ToSlash(oldname)
+	newname = filepath.ToSlash(newname)
+	return e.c.Rename(oldname, newname, false)
+}
+func (e WebDAVFileSystem) Stat(filename string) (os.FileInfo, error) {
+	filename = filepath.ToSlash(filename)
+	return e.c.Stat(filename)
+}
+
+func (e WebDAVFileSystem) VirtualPathToRealPath(subpath string, username string) (string, error) {
+	if strings.HasPrefix(subpath, e.UUID+":/") {
+		//This is full virtual path. Trim the uuid and correct the subpath
+		subpath = subpath[len(e.UUID+":/"):]
+	}
+
+	if e.Hierarchy == "user" {
+		return filepath.ToSlash(filepath.Join("users", username, subpath)), nil
+	} else if e.Hierarchy == "public" {
+		return filepath.ToSlash(subpath), nil
+	}
+	return "", errors.New("unsupported filesystem hierarchy")
+
+}
+func (e WebDAVFileSystem) RealPathToVirtualPath(rpath string, username string) (string, error) {
+	return e.UUID + ":" + filepath.ToSlash(rpath), nil
+}
+func (e WebDAVFileSystem) FileExists(filename string) bool {
+	filename = filepath.ToSlash(filename)
+	_, err := e.c.Stat(filename)
+	if os.IsNotExist(err) || err != nil {
+		return false
+	}
+
+	return true
+}
+func (e WebDAVFileSystem) IsDir(filename string) bool {
+	filename = filepath.ToSlash(filename)
+	s, err := e.c.Stat(filename)
+	if err != nil {
+		return false
+	}
+	return s.IsDir()
+}
+
+//Notes: This is not actual Glob function. This just emulate Glob using ReadDir with max depth 1 layer
+func (e WebDAVFileSystem) Glob(wildcard string) ([]string, error) {
+	fileInfos, err := e.c.ReadDir(filepath.ToSlash(filepath.Clean(filepath.Dir(wildcard))))
+	if err != nil {
+		return []string{}, err
+	}
+
+	validFiles := []string{}
+	for _, fileInfo := range fileInfos {
+		thisFullPath := filepath.ToSlash(filepath.Join(filepath.Dir(wildcard), fileInfo.Name()))
+		match, _ := regexp.MatchString(wildcard, thisFullPath)
+		if match {
+			validFiles = append(validFiles, thisFullPath)
+		}
+	}
+	return validFiles, nil
+}
+func (e WebDAVFileSystem) GetFileSize(filename string) int64 {
+	filename = filepath.ToSlash(filename)
+	s, err := e.Stat(filename)
+	if err != nil {
+		return 0
+	}
+
+	return s.Size()
+}
+func (e WebDAVFileSystem) GetModTime(filename string) (int64, error) {
+	filename = filepath.ToSlash(filename)
+	s, err := e.Stat(filename)
+	if err != nil {
+		return 0, err
+	}
+
+	return s.ModTime().Unix(), nil
+}
+func (e WebDAVFileSystem) WriteFile(filename string, content []byte, mode os.FileMode) error {
+	filename = filepath.ToSlash(filename)
+	return e.c.Write(filename, content, mode)
+}
+func (e WebDAVFileSystem) ReadFile(filename string) ([]byte, error) {
+	filename = filepath.ToSlash(filename)
+	bytes, err := e.c.Read(filename)
+	if err != nil {
+		return []byte(""), err
+	}
+	return bytes, nil
+}
+func (e WebDAVFileSystem) WriteStream(filename string, stream io.Reader, mode os.FileMode) error {
+	filename = filepath.ToSlash(filename)
+	return e.c.WriteStream(filename, stream, mode)
+
+}
+func (e WebDAVFileSystem) ReadStream(filename string) (io.ReadCloser, error) {
+	filename = filepath.ToSlash(filename)
+	return e.c.ReadStream(filename)
+}
+
+func (e WebDAVFileSystem) Walk(rootpath string, walkFn filepath.WalkFunc) error {
+	rootpath = filepath.ToSlash(rootpath)
+	return e.walk(rootpath, walkFn)
+}
+
+func (e WebDAVFileSystem) walk(thisPath string, walkFun filepath.WalkFunc) error {
+	files, err := e.c.ReadDir(thisPath)
+	if err != nil {
+		return err
+	}
+
+	for _, file := range files {
+		thisFileFullPath := filepath.ToSlash(filepath.Join(thisPath, file.Name()))
+		if file.IsDir() {
+			err = walkFun(thisFileFullPath, file, nil)
+			if err != nil {
+				return err
+			}
+			err = e.walk(thisFileFullPath, walkFun)
+			if err != nil {
+				return err
+			}
+		} else {
+			err = walkFun(thisFileFullPath, file, nil)
+			if err != nil {
+				return err
+			}
+		}
+	}
+	return nil
+}

+ 16 - 0
mod/network/websocket/websocket.go

@@ -0,0 +1,16 @@
+package websocket
+
+import "net/http"
+
+type Router struct {
+}
+
+func NewRouter() *Router {
+	return &Router{}
+
+}
+
+func (s *Router) HandleWebSocketRouting(w http.ResponseWriter, r *http.Request) {
+	//WIP
+	http.NotFound(w, r)
+}

+ 1 - 1
mod/storage/ftp/aofs.go

@@ -242,7 +242,7 @@ func (a aofs) pathRewrite(path string) (string, *fs.FileSystemHandler, error) {
 		fsHandlers := a.userinfo.GetAllFileSystemHandler()
 		for _, fsh := range fsHandlers {
 			//Create a folder representation for this virtual directory
-			if !(fsh.Hierarchy == "backup" || fsh.Filesystem == "virtual") {
+			if !(fsh.Hierarchy == "backup" || fsh.IsVirtual()) {
 				os.Mkdir(a.tmpFolder+fsh.UUID, 0755)
 			}
 

+ 33 - 0
mod/storage/storage.go

@@ -9,10 +9,13 @@ package storage
 */
 
 import (
+	"errors"
 	"log"
 	"os"
+	"strings"
 
 	"imuslab.com/arozos/mod/disk/hybridBackup"
+	"imuslab.com/arozos/mod/filesystem"
 	fs "imuslab.com/arozos/mod/filesystem"
 )
 
@@ -98,6 +101,36 @@ func (s *StoragePool) HasHigherOrEqualPermissionThan(a *StoragePool) bool {
 	return true
 }
 
+//Get fsh from virtual path
+func (s *StoragePool) GetFSHandlerFromVirtualPath(vpath string) (*fs.FileSystemHandler, string, error) {
+	fshid, subpath, err := filesystem.GetIDFromVirtualPath(vpath)
+	if err != nil {
+		return nil, subpath, err
+	}
+
+	fsh, err := s.GetFsHandlerByUUID(fshid)
+	if err != nil {
+		return nil, subpath, err
+	}
+
+	return fsh, subpath, nil
+}
+
+func (s *StoragePool) GetFsHandlerByUUID(uuid string) (*fs.FileSystemHandler, error) {
+	//Filter out the :/ fropm uuid if exists
+	if strings.Contains(uuid, ":") {
+		uuid = strings.Split(uuid, ":")[0]
+	}
+
+	for _, fsh := range s.Storages {
+		if fsh.UUID == uuid {
+			return fsh, nil
+		}
+	}
+
+	return nil, errors.New("Filesystem handler with given UUID not found")
+}
+
 //Close all fsHandler under this storage pool
 func (s *StoragePool) Close() {
 	//Close the running backup tasks

+ 7 - 1
module.go

@@ -61,8 +61,14 @@ func ModuleServiceInit() {
 				return
 			}
 
+			fsh, subpath, err := GetFSHandlerSubpathFromVpath(installerPath)
+			if err != nil {
+				common.SendErrorResponse(w, "Invalid installer path")
+				return
+			}
+
 			//Translate it to realpath
-			rpath, err := userinfo.VirtualPathToRealPath(installerPath)
+			rpath, err := fsh.FileSystemAbstraction.VirtualPathToRealPath(subpath, userinfo.Username)
 			if err != nil {
 				log.Println("*Module Installer* Failed to install module: ", err.Error())
 				common.SendErrorResponse(w, "Invalid installer path")

+ 16 - 3
network.go

@@ -11,14 +11,16 @@ import (
 	"imuslab.com/arozos/mod/network/netstat"
 	ssdp "imuslab.com/arozos/mod/network/ssdp"
 	upnp "imuslab.com/arozos/mod/network/upnp"
+	"imuslab.com/arozos/mod/network/websocket"
 	prout "imuslab.com/arozos/mod/prouter"
 	"imuslab.com/arozos/mod/www"
 )
 
 var (
-	MDNS *mdns.MDNSHost
-	UPNP *upnp.UPnPClient
-	SSDP *ssdp.SSDPHost
+	MDNS            *mdns.MDNSHost
+	UPNP            *upnp.UPnPClient
+	SSDP            *ssdp.SSDPHost
+	WebSocketRouter *websocket.Router
 )
 
 func NetworkServiceInit() {
@@ -83,6 +85,17 @@ func NetworkServiceInit() {
 
 	}
 
+	userRouter := prout.NewModuleRouter(prout.RouterOption{
+		AdminOnly:   false,
+		UserHandler: userHandler,
+		DeniedHandler: func(w http.ResponseWriter, r *http.Request) {
+			common.SendErrorResponse(w, "Permission Denied")
+		},
+	})
+
+	WebSocketRouter = websocket.NewRouter()
+	userRouter.HandleFunc("/system/ws", WebSocketRouter.HandleWebSocketRouting)
+
 }
 
 func StartNetworkServices() {

+ 34 - 0
storage.go

@@ -96,6 +96,26 @@ func LoadBaseStoragePool() error {
 	}
 	fsHandlers = append(fsHandlers, tmpHandler)
 
+	/*
+
+		DEBUG REMOVE AFTERWARD
+
+	*/
+	webdh, err := fs.NewFileSystemHandler(fs.FileSystemOption{
+		Name:       "WebDAV",
+		Uuid:       "webdav",
+		Path:       "https://ccns.arozos.com:443/webdav/pub",
+		Access:     "readwrite",
+		Hierarchy:  "public",
+		Automount:  false,
+		Filesystem: "webdav",
+	})
+	if err != nil {
+		log.Println(err.Error())
+		return err
+	}
+	fsHandlers = append(fsHandlers, webdh)
+
 	//Load all the storage config from file
 	rawConfig, err := ioutil.ReadFile(*storage_config_file)
 	if err != nil {
@@ -225,6 +245,20 @@ func GetStoragePoolByOwner(owner string) (*storage.StoragePool, error) {
 	return nil, errors.New("Storage pool owned by " + owner + " not found")
 }
 
+func GetFSHandlerSubpathFromVpath(vpath string) (*fs.FileSystemHandler, string, error) {
+	VirtualRootID, subpath, err := fs.GetIDFromVirtualPath(vpath)
+	if err != nil {
+		return nil, "", errors.New("Unable to resolve requested path: " + err.Error())
+	}
+
+	fsh, err := GetFsHandlerByUUID(VirtualRootID)
+	if err != nil {
+		return nil, "", errors.New("Unable to resolve requested path: " + err.Error())
+	}
+
+	return fsh, subpath, nil
+}
+
 func GetFsHandlerByUUID(uuid string) (*fs.FileSystemHandler, error) {
 	//Filter out the :/ fropm uuid if exists
 	if strings.Contains(uuid, ":") {

部分文件因为文件数量过多而无法显示