package main import ( "encoding/json" "fmt" "os" "os/exec" "path/filepath" "strings" "time" ) type config struct { PasswordCommand string PasswordCommandArgs []string User string DocspellURL string ArchiveDirectory string ImportDirectories []string } func main() { fmt.Println("##################### START #####################") fmt.Println(" Docspell Consumedir Cleaner - v0.1 beta") fmt.Println(" by jacob1123") fmt.Println(" (based on work by totti4ever) ") fmt.Println("#################################################") fmt.Println() // Read config from user profile homeDir, err := os.UserHomeDir() if err != nil { fmt.Println("Error getting user home directory:", err) os.Exit(1) } configFile := filepath.Join(homeDir, "docspell-import.json") configData, err := os.ReadFile(configFile) if err != nil { fmt.Println("Error reading config file:", err) os.Exit(1) } var cfg config if err := json.Unmarshal(configData, &cfg); err != nil { fmt.Println("Error parsing config file:", err) os.Exit(1) } // Get password from command cmd := exec.Command(cfg.PasswordCommand, cfg.PasswordCommandArgs...) password, err := cmd.Output() if err != nil { fmt.Println("Error getting password:", err) os.Exit(1) } validateConfig(cfg) // Login loginCmd := exec.Command("dsc", "login", "--user", cfg.User, "--password", strings.TrimSpace(string(password))) if err := loginCmd.Run(); err != nil { fmt.Println("Login failed:", err) os.Exit(0) } // Check for jq if _, err := exec.LookPath("jq"); err != nil { fmt.Println("please install 'jq'") os.Exit(-4) } dsUrl := cfg.DocspellURL fmt.Println("Settings:") uploadMissing := os.Getenv("DS_CC_UPLOAD_MISSING") == "true" if uploadMissing { fmt.Println(" - UPLOAD files? YES") fmt.Println(" files not existing in Docspell will be uploaded and will be re-checked in the next run.") } else { fmt.Println(" - UPLOAD files? no") fmt.Println(" files not existing in Docspell will NOT be uploaded and stay where they are.") } fmt.Println() fmt.Println() fmt.Println("Press 'ctrl+c' to cancel") time.Sleep(time.Second) fmt.Println() fmt.Println() for _, dsConsumedir := range cfg.ImportDirectories { fmt.Printf("Scanning folder '%s'\n", dsConsumedir) fmt.Println() fmt.Println() err = filepath.Walk(dsConsumedir, func(path string, info os.FileInfo, err error) error { if err != nil { return err } if info.IsDir() { return nil } fmt.Printf("Checking '%s'\n", path) // Check if file exists in Docspell cmd := exec.Command("dsc", "-f", "json", "file-exists", path) output, err := cmd.Output() if err != nil { fmt.Printf("ERROR %v\n", err) return nil } var fileExistsResponse []map[string]interface{} if err := json.Unmarshal(output, &fileExistsResponse); err != nil { fmt.Printf("ERROR parsing response: %v\n", err) return nil } if fileExistsResponse[0]["exists"].(bool) { // File exists in Docspell items := fileExistsResponse[0]["items"].([]interface{}) item := items[0].(map[string]interface{}) itemID := item["id"].(string) itemName := item["name"].(string) // Get item details cmd = exec.Command("dsc", "-f", "json", "item", "get", itemID) output, err = cmd.Output() if err != nil { fmt.Printf("ERROR getting item details: %v\n", err) return nil } var itemDetails map[string]interface{} if err := json.Unmarshal(output, &itemDetails); err != nil { fmt.Printf("ERROR parsing item details: %v\n", err) return nil } folder := itemDetails["folder"].(map[string]interface{})["name"].(string) extension := filepath.Ext(path)[1:] var corr string if corrOrg, ok := itemDetails["corr_org"].(map[string]interface{}); ok && corrOrg["name"] != nil { corr = corrOrg["name"].(string) } else if corrPerson, ok := itemDetails["corr_person"].(map[string]interface{}); ok && corrPerson["name"] != nil { corr = corrPerson["name"].(string) } fmt.Printf("File already exists: '%s @ %s/app/item/%s'\n", itemName, dsUrl, itemID) state := item["state"].(string) if state == "confirmed" { itemDate := item["item_date"] if itemDate == nil { fmt.Println("... but has no date - not doing anything.") } else { timestamp := int64(itemDate.(float64)) / 1000 date := time.Unix(timestamp, 0) curDir := filepath.Join(cfg.ArchiveDirectory, folder, date.Format("2006/01")) if err := os.MkdirAll(curDir, 0755); err != nil { fmt.Printf("ERROR creating directory: %v\n", err) return nil } newPath := filepath.Join(curDir, fmt.Sprintf("%s %s - %s.%s", date.Format("20060102"), corr, itemName, extension)) if err := os.Rename(path, newPath); err != nil { fmt.Printf("ERROR moving file: %v\n", err) return nil } fmt.Printf("... moving to archive by date ('%s')\n", curDir) } } else { fmt.Println("... but is not confirmed yet - not doing anything.") } } else { fmt.Println("Files does not exist, yet") if uploadMissing { fmt.Print("...uploading file..") cmd = exec.Command("dsc", "-f", "json", "upload", path) output, err := cmd.Output() if err != nil { fmt.Printf("\nERROR uploading: %v\n", err) return nil } var uploadResult map[string]interface{} if err := json.Unmarshal(output, &uploadResult); err != nil { fmt.Printf("\nERROR parsing upload result: %v\n", err) return nil } if uploadResult["success"].(bool) { fmt.Println(". done") } else { fmt.Printf("\nERROR %v\n", uploadResult) } } } return nil }) if err != nil { fmt.Printf("Error walking directory: %v\n", err) os.Exit(1) } } fmt.Println("################# DONE #################") fmt.Println(time.Now().Format(time.RFC1123)) } func validateConfig(cfg config) { if len(cfg.ImportDirectories) == 0 || cfg.ArchiveDirectory == "" { fmt.Println("FATAL Parameter missing") fmt.Printf(" import directories: %v\n", cfg.ImportDirectories) fmt.Printf(" archive directory: %s\n", cfg.ArchiveDirectory) os.Exit(-2) } if cfg.User == "" { fmt.Println("FATAL User is missing") os.Exit(-3) } }