package main import ( "crypto/md5" "encoding/hex" "encoding/json" "errors" "fmt" "image" "io" "log" "net/http" "os" "path/filepath" "strings" "github.com/disintegration/imaging" reflect "reflect" ) /* Photo - The worst Photo Viewer for ArOZ Online By Alan Yeung, 2020 */ //SupportFileExt shouldn't be exported var SupportFileExt = []string{".jpg", ".jpeg", ".gif", ".tiff", ".png", ".tif", ".heif"} //Output shouldn't be exported. type Output struct { URL string Filename string Size string CacheURL string Height int Width int } //OutputFolder shouldn't be exported type OutputFolder struct { VPath string Foldername string } //SearchReturn shouldn't be exported type SearchReturn struct { Success bool `json:"success"` Results []struct { Name string `json:"name"` Value string `json:"value"` Text string `json:"text"` Disabled bool `json:"disabled,omitempty"` } `json:"results"` } func module_Photo_init() { http.HandleFunc("/Photo/listPhoto", ModulePhotoListPhoto) http.HandleFunc("/Photo/listFolder", ModulePhotoListFolder) http.HandleFunc("/Photo/search", ModulePhotoSearch) //http.HandleFunc("/Photo/getMeta", module_Photo_getMeta) //Register this module to system registerModule(moduleInfo{ Name: "Photo", Desc: "The Photo Viewer for ArOZ Online", Group: "Media", IconPath: "Photo/img/module_icon.png", Version: "0.0.1", StartDir: "Photo/index.html", SupportFW: true, LaunchFWDir: "Photo/index.html", SupportEmb: true, LaunchEmb: "Photo/embedded.html", InitFWSize: []int{800, 600}, InitEmbSize: []int{800, 600}, SupportedExt: SupportFileExt, }) } //ModulePhotoListPhoto shouldn't be exported func ModulePhotoListPhoto(w http.ResponseWriter, r *http.Request) { username, err := system_auth_getUserName(w, r) if err != nil { redirectToLoginPage(w, r) return } //obtain folder name from GET request folder, ok := r.URL.Query()["folder"] //check if GET request exists, if not then using default path if !ok || len(folder[0]) < 1 { folder = append(folder, "user:/Photo/Photo/uploads") } //obtain filter from GET request filter, ok := r.URL.Query()["q"] //check if GET request exists, if not then using null if !ok || len(folder[0]) < 1 { filter = append(filter, "") } Alldata, _ := QueryDir(folder[0], filter[0], username) jsonString, _ := json.Marshal(Alldata) sendJSONResponse(w, string(jsonString)) } //ModulePhotoListFolder shouldn't be exported func ModulePhotoListFolder(w http.ResponseWriter, r *http.Request) { username, err := system_auth_getUserName(w, r) if err != nil { redirectToLoginPage(w, r) return } storageDir, _ := virtualPathToRealPath("user:/Photo/Photo/storage", username) os.MkdirAll(storageDir, 0755) Alldata := []OutputFolder{} filepath.Walk(storageDir, func(path string, info os.FileInfo, e error) error { if e != nil { return e } if info.Mode().IsDir() && path != storageDir { vPath, _ := realpathToVirtualpath(path, username) folderData := OutputFolder{ VPath: vPath, Foldername: filepath.Base(path), } Alldata = append(Alldata, folderData) } return nil }) jsonString, _ := json.Marshal(Alldata) sendJSONResponse(w, string(jsonString)) } //QueryDir shouldn't be exported. func QueryDir(vPath string, filter string, username string) ([]Output, error) { //create dir uploadDir, _ := virtualPathToRealPath(vPath, username) cacheDir, _ := virtualPathToRealPath("user:/Photo/Photo/thumbnails", username) os.MkdirAll(uploadDir, 0755) os.MkdirAll(cacheDir, 0755) Alldata := []Output{} files, _ := filepath.Glob(uploadDir + "/*") for _, file := range files { if stringInSlice(filepath.Ext(file), SupportFileExt) { if chkFilter(file, filter) { //File path (vpath) vpath, _ := realpathToVirtualpath(file, username) //File Size _, hsize, unit, _ := system_fs_getFileSize(file) size := fmt.Sprintf("%.2f", hsize) + unit //File cache location cacheFilename, _ := resizePhoto(file, username) cacheFilevPath := "user:/Photo/Photo/thumbnails/" + cacheFilename //Get image width height cacheFilePhyPath, _ := virtualPathToRealPath(cacheFilevPath, username) width, height := getImageDimension(cacheFilePhyPath) fileData := Output{ URL: vpath, Filename: filepath.Base(file), Size: size, CacheURL: cacheFilevPath, Height: height, Width: width, } Alldata = append(Alldata, fileData) } } } return Alldata, nil } //ModulePhotoSearch shouldn't be exported func ModulePhotoSearch(w http.ResponseWriter, r *http.Request) { username, err := system_auth_getUserName(w, r) if err != nil { redirectToLoginPage(w, r) return } _ = username //obtain folder name from GET request queryString, ok := r.URL.Query()["q"] QResult := new(SearchReturn) QResult.Success = true //check if GET request exists, if not then using default val if !ok || len(queryString[0]) < 1 { QResult.Success = false } else { n := struct { Name string `json:"name"` Value string `json:"value"` Text string `json:"text"` Disabled bool `json:"disabled,omitempty"` }{Name: "file:" + queryString[0], Value: "file:" + queryString[0], Text: "file:" + queryString[0], Disabled: false} QResult.Results = append(QResult.Results, n) } jsonString, _ := json.Marshal(QResult) sendJSONResponse(w, string(jsonString)) } func resizePhoto(filename string, username string) (string, error) { cacheDir, _ := virtualPathToRealPath("user:/Photo/Photo/thumbnails", username) //Generate hash for that file md5, err := hashFilemd5(filename) if err != nil { return "", err } //check if file exist, if true then return if fileExists(cacheDir + "/" + md5 + ".jpg") { return md5 + ".jpg", nil } // Open image. src, _ := imaging.Open(filename) // Resize the cropped image to width = 200px preserving the aspect ratio. src = imaging.Resize(src, 200, 0, imaging.Lanczos) // Save the resulting image as JPEG. err = imaging.Save(src, cacheDir+"/"+md5+".jpg") if err != nil { log.Fatalf("failed to save image: %v", err) } return md5 + ".jpg", nil } //hashFilemd5 copy from https://mrwaggel.be/post/generate-md5-hash-of-a-file-in-golang/ func hashFilemd5(filePath string) (string, error) { var returnMD5String string file, err := os.Open(filePath) if err != nil { return returnMD5String, err } defer file.Close() hash := md5.New() if _, err := io.Copy(hash, file); err != nil { return returnMD5String, err } hashInBytes := hash.Sum(nil)[:16] returnMD5String = hex.EncodeToString(hashInBytes) return returnMD5String, nil } //thanks https://gist.github.com/sergiotapia/7882944 func getImageDimension(imagePath string) (int, int) { file, err := os.Open(imagePath) if err != nil { //log.Fprintf(os.Stderr, "%v\n", err) } image, _, err := image.DecodeConfig(file) if err != nil { //log.Fprintf(os.Stderr, "%s: %v\n", imagePath, err) } return image.Width, image.Height } var funcs = map[string]interface{}{"file": file} func chkFilter(imagePath string, filter string) bool { if filter == "" { return true } filtersli := strings.Split(filter, ",") Filterbool := make(map[string]bool) if len(filtersli) > 0 { log.Println(filtersli) for _, item := range filtersli { itemArr := strings.Split(item, ":") // [0] = func name , [1] = value log.Println(item) returnResult, _ := Call(funcs, itemArr[0], itemArr[1], imagePath, filepath.Base(imagePath)) Filterbool[item] = Filterbool[item] || returnResult[0].Bool() } } returnBool := true if len(Filterbool) > 0 { for _, item := range Filterbool { returnBool = returnBool && item } } return returnBool } //https://mikespook.com/2012/07/function-call-by-name-in-golang/ //Call shouldn not be exported func Call(m map[string]interface{}, name string, params ...interface{}) (result []reflect.Value, err error) { f := reflect.ValueOf(m[name]) if len(params) != f.Type().NumIn() { err = errors.New("The number of params is not adapted.") return } in := make([]reflect.Value, len(params)) for k, param := range params { in[k] = reflect.ValueOf(param) } result = f.Call(in) return } func file(queryString, filename string, filePath string) bool { if strings.Contains(filename, queryString) { return true } else { return false } }