/* ArOZ Online System - fscov File Naming Format Conversion Tool This micro-servie is designed to convert filename from and to umfilename (ArOZ Online System File Explorer Custom Format) and standard UTF-8 File System filename. Usage: ./fsconv (Convert all the files, folder and sub-directories from and to umfilename to UTF8 filename.) ./fsconv {target} {mode (um / utf)} (Convert all the files, folder and sub-directories to filename in given mode, target can be file or directory.) */ package main import ( "fmt" "io/ioutil" "os" "path" "path/filepath" "reflect" "sort" "strings" "bytes" "encoding/hex" "flag" ) //Commonly used functions func in_array(val interface{}, array interface{}) (exists bool, index int) { exists = false index = -1 switch reflect.TypeOf(array).Kind() { case reflect.Slice: s := reflect.ValueOf(array) for i := 0; i < s.Len(); i++ { if reflect.DeepEqual(val, s.Index(i).Interface()) == true { index = i exists = true return } } } return } func file_exists(path string) bool { if _, err := os.Stat(path); os.IsNotExist(err) { return false } return true } func file_get_contents(path string) string { dat, err := ioutil.ReadFile(path) if err != nil { panic("Unable to read file: " + path) } return (string(dat)) } func scan_recursive(dir_path string, ignore []string) ([]string, []string) { folders := []string{} files := []string{} // Scan filepath.Walk(dir_path, func(path string, f os.FileInfo, err error) error { _continue := false // Loop : Ignore Files & Folders for _, i := range ignore { // If ignored path if strings.Index(path, i) != -1 { // Continue _continue = true } } if _continue == false { f, err = os.Stat(path) // If no error if err != nil { panic("ERROR. " + err.Error()) } // File & Folder Mode f_mode := f.Mode() // Is folder if f_mode.IsDir() { // Append to Folders Array folders = append(folders, path) // Is file } else if f_mode.IsRegular() { // Append to Files Array files = append(files, path) } } return nil }) return folders, files } func hex2bin(s string) ([]byte, error) { ret, err := hex.DecodeString(s) return ret, err } func bin2hex(s string) (string) { src := []byte(s) encodedStr := hex.EncodeToString(src) return encodedStr; } type ByLen []string func (a ByLen) Len() int { return len(a) } func (a ByLen) Less(i, j int) bool { return len(a[i]) > len(a[j]) } func (a ByLen) Swap(i, j int) { a[i], a[j] = a[j], a[i] } func recursiveUTF8Conv(dir string, rf bool, rd bool){ folders, files := scan_recursive(dir, []string{os.Args[0]}) if (rf){ for i := 0; i < len(files); i++ { thisFilepath := strings.ReplaceAll(files[i], "\\", "/") filename := path.Base(thisFilepath) extension := filepath.Ext(filename) name := filename[0 : len(filename)-len(extension)] //Check if it has the inith prefix if len(name) > 5 && name[0:5] == "inith"{ //This is hex filename. Translate its name to normal filename originalName,_ := hex2bin(name[5:]) safeName := getSafeFilename(string(originalName)) fmt.Println(filename + " -> " + safeName + extension) err := os.Rename(files[i], path.Dir(thisFilepath) + "/" + safeName + extension) if (err != nil){ panic(err) } } } } if (rd){ //Sort the array of folders by string length, hence deeper folder will be renamed first sort.Sort(ByLen(folders)) applicationStartupPath, _ := Realpath(path.Dir(os.Args[0])) for j := 0; j < len(folders); j++{ foldername := path.Base(folders[j]) if (foldername != applicationStartupPath && len(filepath.Base(foldername)) > 2){ //All subdirectories except the root if (isHex(filepath.Base(foldername))){ //This folder use hex foldername. Convert it to UTF8 filename convFoldername, _ := hex2bin(filepath.Base(foldername)); safeName := getSafeFilename(string(convFoldername)) fmt.Println(filepath.Base(foldername) + " -> " + string(safeName)) os.Rename(foldername,filepath.Dir(foldername) + "/" + string(safeName)) } } } } } func recursiveUMFNConv(dir string,rf bool, rd bool){ folders, files := scan_recursive(dir, []string{os.Args[0]}) if (rf){ for i := 0; i < len(files); i++ { thisFilepath := strings.ReplaceAll(files[i], "\\", "/") filename := path.Base(thisFilepath) extension := filepath.Ext(filename) name := filename[0 : len(filename)-len(extension)] //Check if it has the inith prefix if len(name) > 5 && name[0:5] != "inith"{ //This is not hex filename. Translate its name to um-filename method umfilename := "inith" + bin2hex(name) fmt.Println(filename + " -> " + umfilename + extension) err := os.Rename(files[i], path.Dir(thisFilepath) + "/" + umfilename + extension) if (err != nil){ panic(err) } } } } if (rd){ //Sort the array of folders by string length, hence deeper folder will be renamed first sort.Sort(ByLen(folders)) applicationStartupPath, _ := Realpath(path.Dir(os.Args[0])) for j := 0; j < len(folders); j++{ foldername := path.Base(folders[j]) if (foldername != applicationStartupPath){ //All subdirectories except the root if (!isHex(filepath.Base(foldername))){ //This folder do not hex foldername. Convert it to hex-foldername convFoldername := bin2hex(filepath.Base(foldername)); fmt.Println(filepath.Base(foldername) + " -> " + string(convFoldername)) os.Rename(foldername,filepath.Dir(foldername) + "/" + string(convFoldername)) } } } } } func isDir(path string) bool{ fi, err := os.Stat(path) if err != nil { fmt.Println(err) return false } switch mode := fi.Mode(); { case mode.IsDir(): // do directory stuff return true case mode.IsRegular(): // do file stuff return false } return false } func getSafeFilename(s string) string{ replacer := strings.NewReplacer("/", "", "\\","", "@","-", "&","-", "*","-", "<","-", ">","-", "|","-", "?","-", ":","-") safeName := replacer.Replace(string(s)) return safeName } func isHex(s string) bool{ _, err := hex2bin(s) if err != nil { return false } return true } //Realpath from https://github.com/yookoala/realpath/blob/master/realpath.go // Realpath returns the real path of a given file in the os func Realpath(fpath string) (string, error) { if len(fpath) == 0 { return "", os.ErrInvalid } if !filepath.IsAbs(fpath) { pwd, err := os.Getwd() if err != nil { return "", err } fpath = filepath.Join(pwd, fpath) } path := []byte(fpath) nlinks := 0 start := 1 prev := 1 for start < len(path) { c := nextComponent(path, start) cur := c[start:] switch { case len(cur) == 0: copy(path[start:], path[start+1:]) path = path[0 : len(path)-1] case len(cur) == 1 && cur[0] == '.': if start+2 < len(path) { copy(path[start:], path[start+2:]) } path = path[0 : len(path)-2] case len(cur) == 2 && cur[0] == '.' && cur[1] == '.': copy(path[prev:], path[start+2:]) path = path[0 : len(path)+prev-(start+2)] prev = 1 start = 1 default: fi, err := os.Lstat(string(c)) if err != nil { return "", err } if isSymlink(fi) { nlinks++ if nlinks > 16 { return "", os.ErrInvalid } var link string link, err = os.Readlink(string(c)) after := string(path[len(c):]) // switch symlink component with its real path path = switchSymlinkCom(path, start, link, after) prev = 1 start = 1 } else { // Directories prev = start start = len(c) + 1 } } } for len(path) > 1 && path[len(path)-1] == os.PathSeparator { path = path[0 : len(path)-1] } return string(path), nil } // test if a link is symbolic link func isSymlink(fi os.FileInfo) bool { return fi.Mode()&os.ModeSymlink == os.ModeSymlink } // switch a symbolic link component to its real path func switchSymlinkCom(path []byte, start int, link, after string) []byte { if link[0] == os.PathSeparator { // Absolute links return []byte(filepath.Join(link, after)) } // Relative links return []byte(filepath.Join(string(path[0:start]), link, after)) } // get the next component func nextComponent(path []byte, start int) []byte { v := bytes.IndexByte(path[start:], os.PathSeparator) if v < 0 { return path } return path[0 : start+v] } func main() { dir, _ := filepath.Abs(filepath.Dir(os.Args[0])) if len(os.Args) < 2 { //Default options, usually double click will start this process recursiveUTF8Conv(dir,true,true) }else if (len(os.Args) == 2 && os.Args[1] == "help"){ //Show help message fmt.Println("ArOZ Online UM File Naming Method Converter") fmt.Println("Usage: \n./fsconv Convert all files under current directory and its sub-directories into UTF-8 file naming method.") fmt.Println("./fsconv -um Convert all files under current directory and its sub-directories into UM File Naming Method representation.") fmt.Println("Use ./fsconv -h for showing all usable flags for defined file / folder conversion") }else if (len(os.Args) == 2 && os.Args[1] == "-um"){ //Convert all files and folder into um filenaming methods recursiveUMFNConv(dir,true,true); } else { //Given target directory with flags var recursive = flag.Bool("r",false,"Enable recursive filename translation.") var inputFile = flag.String("i","","Input filename.") var defaultFormat = flag.Bool("utf",false,"Convert to standard UTF-8 filename format") var umFormat = flag.Bool("um",false,"Convert to UMfilename format") var recursiveFileOnly = flag.Bool("rf",false,"Recursive rename file only.") var recursiveDirOnly = flag.Bool("rd",false,"Recursive rename directory only.") flag.Parse() //Check if the input file exists. if !file_exists(*inputFile){ fmt.Println("ERROR. Input file not exists. Given: ", *inputFile); os.Exit(0); } //Check if setting logic correct if (*defaultFormat == *umFormat){ //If not defined defaultFormat or umFormat, assume default Format *defaultFormat = true } fmt.Println("r:", *recursive) fmt.Println("i:", *inputFile) fmt.Println("utf:", *defaultFormat) fmt.Println("um:", *umFormat) fmt.Println("rf:", *recursiveFileOnly) fmt.Println("rd:", *recursiveDirOnly) //Perform translation process if (*recursive|| *recursiveFileOnly || *recursiveDirOnly){ //Recursive mode if (*defaultFormat == true){ //Convert everything in given directory into standard filename if (*recursiveFileOnly){ recursiveUTF8Conv(*inputFile,true,false) }else if (*recursiveDirOnly){ recursiveUTF8Conv(*inputFile,false,true) }else{ recursiveUTF8Conv(*inputFile,true,true) } }else{ //Convert everything in give ndirectory in umfilename if (*recursiveFileOnly){ recursiveUMFNConv(*inputFile,true,false) }else if (*recursiveDirOnly){ recursiveUMFNConv(*inputFile,false,true) }else{ recursiveUMFNConv(*inputFile,true,true) } } }else{ if (isDir(*inputFile)){ //A folder if (*defaultFormat == true){ if (isHex(filepath.Base(*inputFile))){ //This folder use hex foldername. Convert it to UTF8 filename convFoldername, _ := hex2bin(filepath.Base(*inputFile)); safeName := getSafeFilename(string(convFoldername)) fmt.Println(filepath.Base(*inputFile) + " -> " + string(safeName)) err := os.Rename(*inputFile,filepath.Dir(*inputFile) + "/" + string(safeName)) if (err != nil){ panic(err) } } }else{ convFoldername := bin2hex(filepath.Base(*inputFile)); fmt.Println(filepath.Base(*inputFile) + " -> " + string(convFoldername)) os.Rename(*inputFile,filepath.Dir(*inputFile) + "/" + string(convFoldername)) } }else{ //A file if (*defaultFormat == true){ thisFilepath := strings.ReplaceAll(*inputFile, "\\", "/") filename := path.Base(thisFilepath) extension := filepath.Ext(filename) name := filename[0 : len(filename)-len(extension)] //Check if it has the inith prefix if len(name) > 5 && name[0:5] == "inith"{ //This is hex filename. Translate its name to normal filename originalName,_ := hex2bin(name[5:]) safeName := getSafeFilename(string(originalName)) fmt.Println(filename + " -> " + safeName + extension) err := os.Rename(*inputFile, path.Dir(thisFilepath) + "/" + safeName + extension) if (err != nil){ panic(err) } } }else{ thisFilepath := strings.ReplaceAll(*inputFile, "\\", "/") filename := path.Base(thisFilepath) extension := filepath.Ext(filename) name := filename[0 : len(filename)-len(extension)] //Check if it has the inith prefix if len(name) > 5 && name[0:5] != "inith"{ //This is not hex filename. Translate its name to um-filename method umfilename := "inith" + bin2hex(name) fmt.Println(filename + " -> " + umfilename + extension) err := os.Rename(*inputFile, path.Dir(thisFilepath) + "/" + umfilename + extension) if (err != nil){ panic(err) } } } } } } }