diff --git a/.gitignore b/.gitignore index 66fd13c..ddd33b5 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ *.dll *.so *.dylib +foldest-linux* # Test binary, built with `go test -c` *.test @@ -13,3 +14,5 @@ # Dependency directories (remove the comment below to include it) # vendor/ + +*.code-workspace diff --git a/README.md b/README.md new file mode 100644 index 0000000..c3ff061 --- /dev/null +++ b/README.md @@ -0,0 +1,39 @@ +# Foldest-go + +Automatically manage your folder. + +## How to use + +1. Download binary file in release page +2. Download `conf.yml` and place it in the same folder with binary file +3. Configure settings in `conf.yml`: +```yml +verbose: # verbose output, false as default +targetdir: # the folder you want to manage, you can set it within the program. Last setted folder will be remembered. +tmpbin: + enable: # whether to use tmpbin, false as default + name: tmpbin/ # name of tmpbin, "tmpbin/" as default + treshday: 30 # files not modified for more than this long will be moved into tmpbin, 30 days as default + deleteday: 30 # files in tmpbin for more than this long will be deleted, 30 days as default +``` +4. Set your rules in `rules.yml`: (Currently support 10 rules utmost) +```yml +rule1: + enable: true + name: document + regex: + - ".*?.doc" + - ".*?.docx" + - ".*?.pdf" + threshday: 7 + maxsize: # MB + minsize: # MB +rule2: +... +``` + +## Development progress + +- [ ] Automatic +- [x] Temp trash bin +- [x] Customize rules diff --git a/conf.yml b/conf.yml new file mode 100644 index 0000000..be5477a --- /dev/null +++ b/conf.yml @@ -0,0 +1,9 @@ +verbose: true +targetdir: D:/Download/ +tmpbin: + enable: true + name: tmpbin/ + treshday: 30 + deleteday: 30 + ignore: + - .accelerate diff --git a/foldest.go b/foldest.go new file mode 100644 index 0000000..2a107a5 --- /dev/null +++ b/foldest.go @@ -0,0 +1,29 @@ +package main + +import ( + "fmt" + "foldest-go/utils" +) + +func main() { + conf := utils.ReadConf() + fmt.Println("Press enter to start...") + fmt.Scanln() + + rules := utils.ReadRules() + if rules == nil { + fmt.Println("Skipping classify...") + } else { + utils.DoClassify(rules, conf.Targetdir, conf.Verbose) + } + + if conf.Tmpbin.Enable { + fmt.Println("Performing tmpbin...") + utils.Manage(conf) + } else { + fmt.Println("tmpbin is disabled, skipping...") + } + + fmt.Println("Press enter to exit...") + fmt.Scanln() +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..36140df --- /dev/null +++ b/go.mod @@ -0,0 +1,5 @@ +module foldest-go + +go 1.13 + +require gopkg.in/yaml.v2 v2.2.8 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..0c4da7f --- /dev/null +++ b/go.sum @@ -0,0 +1,3 @@ +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/rules.yml b/rules.yml new file mode 100644 index 0000000..4d59005 --- /dev/null +++ b/rules.yml @@ -0,0 +1,20 @@ +rule1: + enable: true + name: document + regex: + - ".*?.doc" + - ".*?.docx" + - ".*?.pdf" + threshday: 7 + maxsize: + minsize: +rule2: + enable: true + name: films + regex: + - ".*?.mp4" + - ".*?.avi" + - ".*?.flv" + threshday: 7 + maxsize: + minsize: 300 \ No newline at end of file diff --git a/utils/classify.go b/utils/classify.go new file mode 100644 index 0000000..6611b74 --- /dev/null +++ b/utils/classify.go @@ -0,0 +1,113 @@ +package utils + +import ( + "fmt" + "io/ioutil" + "os" + "reflect" + "regexp" + "strings" + "time" + + "gopkg.in/yaml.v2" +) + +// ReadRules : Read rules.yml +func ReadRules() (rules *Rules) { + fmt.Println("Reading conf.yml ...") + rules = new(Rules) + + if _, err := os.Stat("rules.yml"); os.IsNotExist(err) { + fmt.Println("rules.yml not found, skipping ...") + return nil + } + + yamlFile, err := ioutil.ReadFile("rules.yml") + if err != nil { + fmt.Printf("Error while reading rules.yml :\n") + fmt.Printf("\t%c[0;31m%s%c[0m\n", 0x1B, err, 0x1B) + } + err = yaml.Unmarshal(yamlFile, rules) + if err != nil { + fmt.Printf("Error while reading rules.yml :\n") + fmt.Printf("\t%c[0;31m%s%c[0m\n", 0x1B, err, 0x1B) + } + + return rules +} + +// DoClassify : +func DoClassify(rules *Rules, path string, isVerbose bool) { + rType := reflect.TypeOf(rules) + rVal := reflect.ValueOf(rules) + if rType.Kind() == reflect.Ptr { + // 传入的rules是指针,需要.Elem()取得指针指向的value + rType = rType.Elem() + rVal = rVal.Elem() + } else { + panic("rules must be ptr to struct") + } + for i := 0; i < rType.NumField(); i++ { + rule := rVal.Field(i).Interface().(Rule) + if rule.Enable { + doRule(&rule, path, isVerbose) + } + } +} + +// doRule : +func doRule(rule *Rule, path string, isVerbose bool) { + fmt.Printf("Performing rule %c[0;33m%s%c[0m ...\n", 0x1B, rule.Name, 0x1B) + if !strings.HasSuffix(rule.Name, "/") { + rule.Name = rule.Name + "/" + } + _, err := os.Stat(path + rule.Name) + if err != nil { + fmt.Printf("Making folder %c[0;33m%s%c[0m ...\n", 0x1B, rule.Name, 0x1B) + err := os.Mkdir(path+rule.Name, 0777) + if err != nil { + fmt.Printf("Error while making folder %c[0;33m%s%c[0m ...\n", 0x1B, rule.Name, 0x1B) + fmt.Printf("\t%c[0;31m%s%c[0m\n", 0x1B, err, 0x1B) + } + } + + dir := OpenDir(path) + if dir == nil { + return + } + + // Moving files to rule dir + for _, file := range dir { + // jump rule dir + if file.Name() == rule.Name || file.IsDir() { + continue + } + + for _, pattern := range rule.Regex { + re := regexp.MustCompile(pattern) + match := re.MatchString(file.Name()) + if match { + modTime, strerr := GetFileModTime(path + file.Name()) + if strerr == "" { + if isVerbose { + fmt.Printf("%c[0;34m%s%c[0m %c[0;32m%s%c[0m %d\n", 0x1B, file.Name(), 0x1B, 0x1B, modTime, 0x1B, file.Size()) + } + // If file reaches deleteday + if time.Now().Unix()-modTime.Unix() >= int64(rule.Thresh*86400) { + if (rule.Maxsize <= 0 || file.Size() < (int64)(rule.Maxsize)*1024*1024) && file.Size() > (int64)(rule.Minsize)*1024*1024 { + if isVerbose { + fmt.Printf("%c[0;34m%s%c[0m matches %c[0;33m%s%c[0m\n", 0x1B, file.Name(), 0x1B, 0x1B, rule.Name, 0x1B) + } + src := path + file.Name() + des := path + rule.Name + file.Name() + MoveAll(file, src, des) + } + } + } else { + fmt.Printf("Error while scanning %c[0;34m%s%c[0m :", 0x1B, file.Name(), 0x1B) + fmt.Printf("\t%c[0;31m%s%c[0m\n", 0x1B, err, 0x1B) + } + } + } + } +} diff --git a/utils/conf.go b/utils/conf.go new file mode 100644 index 0000000..01a5709 --- /dev/null +++ b/utils/conf.go @@ -0,0 +1,100 @@ +package utils + +import ( + "fmt" + "io/ioutil" + "os" + "strings" + + "gopkg.in/yaml.v2" +) + +// ReadConf : Read conf.yml +func ReadConf() (conf *Conf) { + fmt.Println("Reading conf.yml ...") + conf = new(Conf) + if _, err := os.Stat("conf.yml"); os.IsNotExist(err) { + fmt.Println("conf.yml not found, starting with default value ...") + } else { + yamlFile, err := ioutil.ReadFile("conf.yml") + if err != nil { + fmt.Printf("Error while reading conf.yml :\n") + fmt.Printf("\t%c[0;31m%s%c[0m\n", 0x1B, err, 0x1B) + } + err = yaml.Unmarshal(yamlFile, conf) + if err != nil { + fmt.Printf("Error while reading conf.yml :\n") + fmt.Printf("\t%c[0;31m%s%c[0m\n", 0x1B, err, 0x1B) + } + } + + // Set path + var isChanged bool + isChanged = SetPath(&conf.Targetdir) + + // Set default values + SetDefault(conf) + + if isChanged { + SaveConf(conf) + } + + return conf +} + +// SetPath : Set target dir +func SetPath(path *string) (isChanged bool) { + isChanged = false + + for { + if *path == "" { + fmt.Println("Please input path of the target folder:") + fmt.Scanln(path) + isChanged = true + } + + if !strings.HasSuffix(*path, "/") { + *path = *path + "/" + isChanged = true + } + + if CheckDir(*path) { + break + } + } + + return isChanged +} + +// SetDefault : Set default value of the conf +func SetDefault(conf *Conf) { + if conf.Tmpbin.Name == "" { + conf.Tmpbin.Name = "tmpbin/" + } + if !strings.HasSuffix(conf.Tmpbin.Name, "/") { + conf.Tmpbin.Name = conf.Tmpbin.Name + "/" + } + + if conf.Tmpbin.Thresh == 0 { + conf.Tmpbin.Thresh = 30 + } + + if conf.Tmpbin.Delete == 0 { + conf.Tmpbin.Delete = 30 + } + + if len(conf.Tmpbin.Ignore) == 0 { + conf.Tmpbin.Ignore = append(conf.Tmpbin.Ignore, ".accelerate") + } +} + +// SaveConf : Save the conf.yml +func SaveConf(conf *Conf) { + fmt.Println("Saving conf.yml ...") + yamlChanged, err := yaml.Marshal(conf) + if err != nil { + fmt.Printf("Error while saving conf.yml :\n") + fmt.Printf("\t%c[0;31m%s%c[0m\n", 0x1B, err, 0x1B) + } + err = ioutil.WriteFile("conf.yml", yamlChanged, 0644) +} diff --git a/utils/file.go b/utils/file.go new file mode 100644 index 0000000..1095e15 --- /dev/null +++ b/utils/file.go @@ -0,0 +1,99 @@ +package utils + +import ( + "fmt" + "io" + "io/ioutil" + "os" + "strings" + "time" +) + +// GetFileModTime :获取文件修改时间 返回时间 +func GetFileModTime(path string) (t time.Time, strerr string) { + f, err := os.Open(path) + if err != nil { + return time.Now(), "open file error" + } + defer f.Close() + + fi, err := f.Stat() + if err != nil { + return time.Now(), "stat fileinfo error" + } + + return fi.ModTime(), "" +} + +// CopyFile : via io.Copy +func CopyFile(src, des string) (written int64, err error) { + srcFile, err := os.Open(src) + if err != nil { + return 0, err + } + defer srcFile.Close() + + //获取源文件的权限 + fi, _ := srcFile.Stat() + perm := fi.Mode() + + //desFile, err := os.Create(des) //无法复制源文件的所有权限 + desFile, err := os.OpenFile(des, os.O_RDWR|os.O_CREATE|os.O_TRUNC, perm) //复制源文件的所有权限 + if err != nil { + return 0, err + } + defer desFile.Close() + + return io.Copy(desFile, srcFile) +} + +// MoveAll : Move a file or folder +func MoveAll(file os.FileInfo, src, des string) { + // Check if file already existed in rule dir + if _, err := os.Stat(des); !os.IsNotExist(err) { + fmt.Printf("Error while moving %c[0;34m%s%c[0m :", 0x1B, file.Name(), 0x1B) + fmt.Printf("\t%c[0;31m%s already existed in %s%c[0m\n", 0x1B, file.Name(), strings.Replace(des, file.Name(), "", 1), 0x1B) + } else { + if !file.IsDir() { // file, not folder + _, err := CopyFile(src, des) + if err != nil { + fmt.Printf("Error while moving %c[0;34m%s%c[0m :", 0x1B, file.Name(), 0x1B) + fmt.Printf("\t%c[0;31m%s%c[0m\n", 0x1B, err, 0x1B) + } + err = os.Remove(src) + if err != nil { + fmt.Printf("Error while moving %c[0;34m%s%c[0m :", 0x1B, file.Name(), 0x1B) + fmt.Printf("\t%c[0;31m%s%c[0m\n", 0x1B, err, 0x1B) + } + } else { // folder + err := os.Rename(src, des) + if err != nil { + fmt.Printf("Error while moving %c[0;34m%s%c[0m :", 0x1B, file.Name(), 0x1B) + fmt.Printf("\t%c[0;31m%s%c[0m\n", 0x1B, err, 0x1B) + } + } + } +} + +// CheckDir : Check if path is a folder +func CheckDir(path string) (isDir bool) { + info, err := os.Stat(path) + if err != nil || !info.IsDir() { + fmt.Printf("Error while scanning %c[0;34m%s%c[0m :", 0x1B, path, 0x1B) + fmt.Printf("\t%c[0;31m%s%c[0m\n", 0x1B, err, 0x1B) + return false + } + return true +} + +//OpenDir : Open a folder +func OpenDir(path string) (dir []os.FileInfo) { + fmt.Printf("Scanning %c[0;34m%s%c[0m ...\n", 0x1B, path, 0x1B) + dir, err := ioutil.ReadDir(path) + if err != nil { + fmt.Printf("Error while scanning %c[0;34m%s%c[0m :", 0x1B, path, 0x1B) + fmt.Printf("\t%c[0;31m%s%c[0m\n", 0x1B, err, 0x1B) + return nil + } + return dir +} diff --git a/utils/tmpbin.go b/utils/tmpbin.go new file mode 100644 index 0000000..4a25a01 --- /dev/null +++ b/utils/tmpbin.go @@ -0,0 +1,93 @@ +package utils + +import ( + "fmt" + "os" + "time" +) + +// Manage folder +func Manage(conf *Conf) { + // Make dir 'tmpbin/' + _, err := os.Stat(conf.Targetdir + conf.Tmpbin.Name) + if err != nil { + fmt.Printf("Making folder %c[0;34m%s%c[0m ...\n", 0x1B, conf.Tmpbin.Name, 0x1B) + err := os.Mkdir(conf.Targetdir+conf.Tmpbin.Name, 0777) + if err != nil { + fmt.Printf("Error while making folder %c[0;34m%s%c[0m ...\n", 0x1B, conf.Tmpbin.Name, 0x1B) + fmt.Printf("\t%c[0;31m%s%c[0m\n", 0x1B, err, 0x1B) + } + } + + dir := OpenDir(conf.Targetdir) + if dir == nil { + return + } + + // Moving files to tmpbin + for count, file := range dir { + if count > 10 { + break + } + modTime, strerr := GetFileModTime(conf.Targetdir + file.Name()) + if strerr == "" { + // jump tmpbin + if file.Name() == conf.Tmpbin.Name { + continue + } + + if conf.Verbose { + fmt.Printf("%c[0;34m%s%c[0m %c[0;32m%s%c[0m\n", 0x1B, file.Name(), 0x1B, 0x1B, modTime, 0x1B) + } + + // If file reaches thresh + if time.Now().Unix()-modTime.Unix() >= int64(conf.Tmpbin.Thresh*86400) { + //if conf.Verbose { + fmt.Printf("Moving %c[0;34m%s%c[0m\n", 0x1B, file.Name(), 0x1B) + //} + src := conf.Targetdir + file.Name() + des := conf.Targetdir + conf.Tmpbin.Name + file.Name() + MoveAll(file, src, des) + } + + } else { + fmt.Printf("Error while scanning %c[0;34m%s%c[0m :", 0x1B, file.Name(), 0x1B) + fmt.Printf("\t%c[0;31m%s%c[0m\n", 0x1B, err, 0x1B) + } + } + + // Delete files in tmpbin/ + dir = OpenDir(conf.Targetdir + conf.Tmpbin.Name) + if dir == nil { + return + } + for _, file := range dir { + modTime, strerr := GetFileModTime(conf.Targetdir + conf.Tmpbin.Name + file.Name()) + if strerr == "" { + if conf.Verbose { + fmt.Printf("%c[0;34m%s%c[0m %c[0;32m%s%c[0m\n", 0x1B, file.Name(), 0x1B, 0x1B, modTime, 0x1B) + } + + // If file reaches deleteday + if time.Now().Unix()-modTime.Unix() >= int64(conf.Tmpbin.Delete*86400) { + //if conf.Verbose { + fmt.Printf("Deleting %c[0;34m%s%c[0m\n", 0x1B, file.Name(), 0x1B) + //} + src := conf.Targetdir + conf.Tmpbin.Name + file.Name() + if file.IsDir() { + err = os.RemoveAll(src) + } else { + err = os.Remove(src) + } + + if err != nil { + fmt.Printf("Error while deleting %c[0;34m%s%c[0m :", 0x1B, file.Name(), 0x1B) + fmt.Printf("\t%c[0;31m%s%c[0m\n", 0x1B, err, 0x1B) + } + } + } else { + fmt.Printf("Error while scanning %c[0;34m%s%c[0m :", 0x1B, file.Name(), 0x1B) + fmt.Printf("\t%c[0;31m%s%c[0m\n", 0x1B, err, 0x1B) + } + } +} diff --git a/utils/yaml.go b/utils/yaml.go new file mode 100644 index 0000000..7274dc6 --- /dev/null +++ b/utils/yaml.go @@ -0,0 +1,38 @@ +package utils + +// Conf : a struct for conf.yml +type Conf struct { + Verbose bool `yaml:"verbose"` + Targetdir string `yaml:"targetdir"` + Tmpbin struct { + Enable bool `yaml:"enable"` + Name string `yaml:"name"` + Thresh int `yaml:"treshday"` + Delete int `yaml:"deleteday"` + Ignore []string `yaml:"ignore"` + } +} + +// Rule : a template struct for a rule +type Rule struct { + Enable bool `yaml:"enable"` + Name string `yaml:"name"` + Regex []string `yaml:"regex"` + Thresh int `yaml:"threshday"` + Maxsize int `yaml:"maxsize"` + Minsize int `yaml:"minsize"` +} + +// Rules : +type Rules struct { + Rule1 Rule `yaml:"rule1"` + Rule2 Rule `yaml:"rule2"` + Rule3 Rule `yaml:"rule3"` + Rule4 Rule `yaml:"rule4"` + Rule5 Rule `yaml:"rule5"` + Rule6 Rule `yaml:"rule6"` + Rule7 Rule `yaml:"rule7"` + Rule8 Rule `yaml:"rule8"` + Rule9 Rule `yaml:"rule9"` + Rule10 Rule `yaml:"rule10"` +}