docspell-import/main.go
2025-01-03 00:26:43 +01:00

266 lines
6.9 KiB
Go

package main
import (
"encoding/json"
"flag"
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"time"
)
type config struct {
PasswordCommand string
PasswordCommandArgs []string
User string
DocspellURL string
ArchiveDirectory string
ImportDirectories []string
}
type FileExistsResult struct {
Exists bool `json:"exists"`
Items []DocspellItem `json:"items"`
File string `json:"file"`
}
type DocspellItem struct {
ID string `json:"id"`
Name string `json:"name"`
Direction string `json:"direction"`
State string `json:"state"`
Created int64 `json:"created"`
ItemDate int64 `json:"item_date"`
}
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()
uploadMissing := flag.Bool("upload-missing", os.Getenv("DS_CC_UPLOAD_MISSING") == "true", "true, to upload files to docspell that do not yet exist there")
flag.Parse()
// 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)
}
fmt.Println("Settings:")
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)
for _, dsConsumedir := range cfg.ImportDirectories {
fmt.Println()
fmt.Println()
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 []FileExistsResult
if err := json.Unmarshal(output, &fileExistsResponse); err != nil {
fmt.Printf(" ERROR parsing response: %v\n", err)
return nil
}
if fileExistsResponse[0].Exists {
err := handleExistingFile(cfg, fileExistsResponse[0])
if err != nil {
fmt.Println(" ERROR", err)
}
} 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("\n ERROR uploading: %v\n", err)
return nil
}
var uploadResult map[string]interface{}
if err := json.Unmarshal(output, &uploadResult); err != nil {
fmt.Printf("\n ERROR parsing upload result: %v\n", err)
return nil
}
if uploadResult["success"].(bool) {
fmt.Println(". done")
} else {
fmt.Printf("\n ERROR %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 handleExistingFile(cfg config, fileExistsResponse FileExistsResult) error {
// File exists in Docspell
items := fileExistsResponse.Items
item := items[0]
itemID := item.ID
itemName := item.Name
// Get item details
cmd := exec.Command("dsc", "-f", "json", "item", "get", itemID)
output, err := cmd.Output()
if err != nil {
return fmt.Errorf("get item details: %w", err)
}
var itemDetails DocspellItemDetails
if err := json.Unmarshal(output, &itemDetails); err != nil {
return fmt.Errorf("parse item details: %w", err)
}
folder := "null"
if itemDetails.Folder != nil {
folder = itemDetails.Folder.Name
}
extension := filepath.Ext(fileExistsResponse.File)[1:]
var corr string
if itemDetails.CorrespondingOrganisation != nil && itemDetails.CorrespondingOrganisation.Name != "" {
corr = itemDetails.CorrespondingOrganisation.Name
} else if itemDetails.CorrespondingPerson != nil && itemDetails.CorrespondingPerson.Name != "" {
corr = itemDetails.CorrespondingPerson.Name
}
fmt.Printf(" File already exists: '%s @ %s/app/item/%s'\n", itemName, cfg.DocspellURL, itemID)
state := item.State
if state != "confirmed" {
fmt.Println(" ... but is not confirmed yet - not doing anything.")
return nil
}
itemDate := item.ItemDate
if itemDate == 0 {
fmt.Println(" ... but has no date - not doing anything.")
return nil
}
date := time.Unix(itemDate/1000, 0)
curDir := filepath.Join(cfg.ArchiveDirectory, folder, date.Format("2006/01"))
if err := os.MkdirAll(curDir, 0755); err != nil {
return fmt.Errorf("create directory: %w", err)
}
subfolder := fmt.Sprintf("%s %s - %s.%s", date.Format("20060102"), corr, itemName, extension)
newPath := filepath.Join(curDir, subfolder)
if err := MoveFile(fileExistsResponse.File, newPath); err != nil {
return fmt.Errorf("move file: %w", err)
}
fmt.Printf(" ... moving to archive by date ('%s')\n", curDir)
return nil
}
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)
}
}
type DocspellItemDetails struct {
DocspellItem
CorrespondingOrganisation *DocspellEntity `json:"corr-org"`
CorrespondingPerson *DocspellEntity `json:"corr-person"`
Folder *DocspellEntity `json:"folder"`
}
type DocspellEntity struct {
ID string `json:"id"`
Name string `json:"name"`
}