probably finished project

This commit is contained in:
Daniel Sommer 2023-08-15 14:51:54 +02:00
parent 646732fa5f
commit 6b08d2807b
11 changed files with 289 additions and 10 deletions

4
.gitignore vendored
View file

@ -1,2 +1,2 @@
__debug_bin
worklog
__debug_bin*
dedupe

7
.vscode/launch.json vendored
View file

@ -8,10 +8,11 @@
"mode": "auto",
"program": "${workspaceFolder}/main.go",
"args": [
"/tmp/nfs/music/lossless",
"/tmp/nfs/music/mp3",
"/home/velvettear/music/lossless",
"/home/velvettear/music/mp3",
"-v",
"--delete"
"-m",
"/home/velvettear/music/duplicates"
]
}
]

View file

@ -1,3 +1,13 @@
# dedupe
simple command line tool to find and move/delete duplicate audio files
simple command line tool to find and move/delete duplicate audio files
## run
`dedupe [source] [comparison] (options)`
### options
- -d | --delete: delete duplicate files
- -m | --move: move duplicate files to specified directory
- -v | --verbose: enable verbose / debug output

55
files/cleaner.go Normal file
View file

@ -0,0 +1,55 @@
package files
import (
"io/fs"
"os"
"path/filepath"
"sort"
"strconv"
"strings"
"time"
"velvettear/dedupe/log"
)
var subdirectories []string
// exported function(s)
func RemoveEmptyDirectories(directory string) {
subdirectories = nil
timestamp := time.Now()
log.Info("deleting empty subdirectories...", "directory: "+directory)
filepath.WalkDir(directory, collectSubdirectories)
sort.Slice(subdirectories, func(i, j int) bool {
return strings.Count(subdirectories[i], string(os.PathSeparator)) > strings.Count(subdirectories[j], string(os.PathSeparator))
})
count := 0
for _, subdirectory := range subdirectories {
files, error := os.ReadDir(subdirectory)
if error != nil {
log.Error("encountered an error checking the content of directory '" + subdirectory)
}
if len(files) > 0 {
continue
}
error = os.Remove(subdirectory)
if error != nil {
log.Warning("encountered an error deleting directory '"+subdirectory+"'", error.Error())
continue
}
count++
log.Debug("deleted directory '" + subdirectory + "'")
}
log.InfoTimed("finished deleting "+strconv.Itoa(count)+" empty subdirectoies", timestamp.UnixMilli(), "directory: "+directory)
}
// unexported function(s)
func collectSubdirectories(path string, dir fs.DirEntry, err error) error {
if err != nil {
return err
}
if !dir.IsDir() {
return nil
}
subdirectories = append(subdirectories, path)
return nil
}

39
files/mover.go Normal file
View file

@ -0,0 +1,39 @@
package files
import (
"io/fs"
"os"
"path/filepath"
"strings"
"time"
"velvettear/dedupe/log"
"velvettear/dedupe/settings"
)
// exported function(s)
func MoveFile(file string) bool {
timestamp := time.Now()
targetFile := filepath.Join(settings.MoveDirectory, strings.Replace(file, settings.ComparisonDirectory, "", 1))
targetDirectory := filepath.Dir(targetFile)
error := createTargetDirectory(targetDirectory, 0777)
if error != nil {
log.Error("encountered an error creating the directory '"+targetDirectory+"'", error.Error())
return false
}
error = os.Rename(file, targetFile)
if error != nil {
log.Error("encountered an error moving the file '"+file+"' to '"+targetFile+"'", error.Error())
return false
}
log.DebugTimed("moved file '"+file+"' to '"+targetFile+"'", timestamp.UnixMilli())
return true
}
func createTargetDirectory(directory string, permissions fs.FileMode) error {
error := os.MkdirAll(directory, permissions)
if error != nil {
return error
}
log.Debug("created directory '" + directory + "'")
return nil
}

View file

@ -4,11 +4,14 @@ import (
"io/fs"
"path/filepath"
"strconv"
"strings"
"time"
"velvettear/dedupe/log"
"velvettear/dedupe/settings"
)
var Duplicates []string
var sourceFiles []string
var comparisonFiles []string
@ -24,11 +27,21 @@ func Scan() {
filepath.WalkDir(settings.ComparisonDirectory, fillComparisonFiles)
log.InfoTimed("found "+strconv.Itoa(len(comparisonFiles))+" comparison files", timestamp.UnixMilli())
timestamp = time.Now()
log.Info("comparing files...")
for _, sourceFile := range sourceFiles {
log.Debug("checking file", sourceFile)
sourceFileName := filepath.Base(sourceFile)
log.Debug("derp", sourceFileName)
sourceFilePath := strings.Replace(strings.Replace(sourceFile, filepath.Ext(sourceFile), "", 1), settings.SourceDirectory, "", 1)
for _, comparisonFile := range comparisonFiles {
comparisonFilePath := strings.Replace(strings.Replace(comparisonFile, filepath.Ext(comparisonFile), "", 1), settings.ComparisonDirectory, "", 1)
if comparisonFilePath != sourceFilePath {
continue
}
log.Debug("duplicate file found", sourceFile+" -> "+comparisonFile)
Duplicates = append(Duplicates, comparisonFile)
}
}
log.InfoTimed("found "+strconv.Itoa(len(Duplicates))+" duplicate files", timestamp.Local().UnixMilli())
}
// unexported function(s)

2
go.mod
View file

@ -1,3 +1,3 @@
module velvettear/dedupe
go 1.20
go 1.21

61
main.go
View file

@ -1,11 +1,72 @@
package main
import (
"os"
"strconv"
"time"
"velvettear/dedupe/files"
"velvettear/dedupe/log"
"velvettear/dedupe/prompts"
"velvettear/dedupe/settings"
)
func main() {
timestamp := time.Now()
settings.Initialize()
log.Info("starting dedupe...")
files.Scan()
if len(files.Duplicates) == 0 {
log.Info("no duplicate files have been found - exiting...")
os.Exit(0)
}
prompts.ListDuplicates()
var proceed bool
var deleteDuplicates bool
if len(settings.MoveDirectory) == 0 && !settings.Delete {
exit(timestamp, 0)
}
if len(settings.MoveDirectory) > 0 && settings.Delete {
proceed, deleteDuplicates = prompts.DeleteOrMove()
} else {
proceed = prompts.HandleDuplicates(settings.Delete)
}
if !proceed {
exit(timestamp, 0)
}
counter := 0
subtimestamp := time.Now()
if deleteDuplicates {
log.Info("deleting duplicate files in '" + settings.ComparisonDirectory + "'...")
for _, file := range files.Duplicates {
subsubtimestamp := time.Now()
error := os.Remove(file)
if error != nil {
log.Error("encountered an error deleting the file '"+file+"'", error.Error())
continue
}
counter++
log.DebugTimed("deleted the file '"+file+"'", subsubtimestamp.UnixMilli())
}
log.InfoTimed("deleted "+strconv.Itoa(counter)+" duplicate files to '"+settings.MoveDirectory+"'", subtimestamp.UnixMilli())
} else {
log.Info("moving duplicate files to '" + settings.MoveDirectory + "'...")
for _, file := range files.Duplicates {
if files.MoveFile(file) {
counter++
}
}
log.InfoTimed("moved "+strconv.Itoa(counter)+" duplicate files to '"+settings.MoveDirectory+"'", subtimestamp.UnixMilli())
}
if prompts.DeleteEmptyDirectories(settings.SourceDirectory) {
files.RemoveEmptyDirectories(settings.SourceDirectory)
}
if prompts.DeleteEmptyDirectories(settings.ComparisonDirectory) {
files.RemoveEmptyDirectories(settings.ComparisonDirectory)
}
exit(timestamp, 0)
}
func exit(timestamp time.Time, code int) {
log.InfoTimed("dedupe finished - exiting...", timestamp.UnixMilli())
os.Exit(code)
}

78
prompts/prompts.go Normal file
View file

@ -0,0 +1,78 @@
package prompts
import (
"bufio"
"os"
"strings"
"velvettear/dedupe/files"
"velvettear/dedupe/log"
"velvettear/dedupe/settings"
)
func ListDuplicates() {
log.Info("would you like to list all duplicate files? [y]es | [n]o")
reader := bufio.NewReader(os.Stdin)
input, error := reader.ReadString('\n')
if error != nil {
log.Fatal("encountered an error reading the input", error.Error())
}
input = strings.ToLower(strings.TrimSpace(strings.TrimSuffix(input, "\n")))
if input != "y" && input != "yes" {
return
}
for _, duplicate := range files.Duplicates {
log.Info(duplicate)
}
}
func HandleDuplicates(delete bool) bool {
var msg string
if delete {
msg = "are you sure you want to delete all duplicate files? [y]es | [n]o"
} else {
msg = "are you sure you want to move all duplicate files to '" + settings.MoveDirectory + "'? [y]es | [no]"
}
log.Info(msg)
reader := bufio.NewReader(os.Stdin)
input, error := reader.ReadString('\n')
if error != nil {
log.Fatal("encountered an error reading the input", error.Error())
}
input = strings.ToLower(strings.TrimSpace(strings.TrimSuffix(input, "\n")))
return input == "y" || input == "yes"
}
func DeleteOrMove() (confirmed bool, delete bool) {
log.Warning("parameters for both actions 'delete' and 'move' are set")
log.Info("do you want to delete or move all duplicate files? [d]elete | [m]ove")
reader := bufio.NewReader(os.Stdin)
input, error := reader.ReadString('\n')
if error != nil {
log.Fatal("encountered an error reading the input", error.Error())
}
input = strings.ToLower(strings.TrimSpace(strings.TrimSuffix(input, "\n")))
switch input {
case "d":
fallthrough
case "delete":
return HandleDuplicates(true), true
case "m":
fallthrough
case "move":
return HandleDuplicates(false), false
default:
log.Fatal("input not recognized")
}
return false, false
}
func DeleteEmptyDirectories(directory string) bool {
log.Info("do you want to delete all empty subdirectories in '" + directory + "'? [y]es | [n]o")
reader := bufio.NewReader(os.Stdin)
input, error := reader.ReadString('\n')
if error != nil {
log.Fatal("encountered an error reading the input", error.Error())
}
input = strings.ToLower(strings.TrimSpace(strings.TrimSuffix(input, "\n")))
return input == "y" || input == "yes"
}

View file

@ -11,9 +11,17 @@ func Initialize() {
if len(os.Args) < 3 {
log.Fatal("error: missing arguments")
}
for _, arg := range os.Args {
for index, arg := range os.Args {
arg = strings.ToLower(arg)
switch arg {
case "-m":
fallthrough
case "--move":
moveDirectoryIndex := index + 1
if moveDirectoryIndex >= len(os.Args) {
log.Fatal("no move directory given")
}
setMoveDirectory(os.Args[index+1])
case "-d":
fallthrough
case "--delete":

View file

@ -1,6 +1,7 @@
package settings
import (
"os"
"strconv"
"velvettear/dedupe/log"
)
@ -10,6 +11,7 @@ var Verbose bool
var Delete bool
var SourceDirectory string
var ComparisonDirectory string
var MoveDirectory string
// unexported function(s)
func setVerbose(verbose bool) {
@ -32,3 +34,15 @@ func setComparisonDirectory(directory string) {
ComparisonDirectory = directory
log.Debug("set source directory", ComparisonDirectory)
}
func setMoveDirectory(directory string) {
stats, error := os.Stat(directory)
if error != nil && os.IsNotExist(error) {
log.Fatal("given move directory '"+directory+"' does not exist", error.Error())
}
if !stats.IsDir() {
log.Fatal("given move directory '" + directory + "' is not a valid directory")
}
MoveDirectory = directory
log.Debug("set move directory", MoveDirectory)
}