Compare commits
1 Commits
1c23ad0cfd
...
dev
Author | SHA1 | Date | |
---|---|---|---|
195c4ab3ad |
5
.gitignore
vendored
5
.gitignore
vendored
@@ -1,3 +1,2 @@
|
|||||||
*.exe
|
|
||||||
*.exe
|
main.exe
|
||||||
cln
|
|
||||||
|
35
README.md
35
README.md
@@ -1,35 +0,0 @@
|
|||||||
# synclib
|
|
||||||
|
|
||||||
A small Go tool for creating symbolic links
|
|
||||||
|
|
||||||
Created out of infuriating difficulty of creating symbolic links on windows
|
|
||||||
|
|
||||||
## Custom syntax
|
|
||||||
|
|
||||||
The tool works with "instructions" that describe symbolic links
|
|
||||||
|
|
||||||
They are, in any form, \<source>,\<destination>,\<force?>
|
|
||||||
|
|
||||||
For example:
|
|
||||||
`sync this,that`
|
|
||||||
|
|
||||||
It supports input of these instructions through:
|
|
||||||
|
|
||||||
- Stdin
|
|
||||||
- `echo "this,that" | sync`
|
|
||||||
- Run arguments
|
|
||||||
- `sync this,that foo,bar "foo 2","C:/bar"`
|
|
||||||
- Files
|
|
||||||
- `sync -f <file>`
|
|
||||||
- Where the file contains instructions, one instruction per line
|
|
||||||
- Directories
|
|
||||||
- `sync -r <directory>`
|
|
||||||
- This mode will look for "sync" files recursively in directories and run their instructions
|
|
||||||
|
|
||||||
## Use case
|
|
||||||
|
|
||||||
I have a lot of folders (documents, projects, configurations) backed up via Seafile and to have the software using those folders find them at their usual location I'm creating soft symbolic links from the seafile drive to their original location
|
|
||||||
|
|
||||||
It would be problematic to have to redo all (or some part) of these symlinks when reinstalling the OS or having something somewhere explode (say software uninstalled) so I have all the instructions in sync files in individual folders in the seafile drive
|
|
||||||
|
|
||||||
Which means I can easily back up my configuration and `sync -r ~/Seafile` to symlink it where it belongs
|
|
99
colors.go
99
colors.go
@@ -1,99 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"math/rand/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// Reset
|
|
||||||
Reset = "\033[0m" // Text Reset
|
|
||||||
|
|
||||||
// Regular Colors
|
|
||||||
Black = "\033[0;30m" // Black
|
|
||||||
Red = "\033[0;31m" // Red
|
|
||||||
Green = "\033[0;32m" // Green
|
|
||||||
Yellow = "\033[0;33m" // Yellow
|
|
||||||
Blue = "\033[0;34m" // Blue
|
|
||||||
Purple = "\033[0;35m" // Purple
|
|
||||||
Cyan = "\033[0;36m" // Cyan
|
|
||||||
White = "\033[0;37m" // White
|
|
||||||
|
|
||||||
// Bold
|
|
||||||
BBlack = "\033[1;30m" // Black
|
|
||||||
BRed = "\033[1;31m" // Red
|
|
||||||
BGreen = "\033[1;32m" // Green
|
|
||||||
BYellow = "\033[1;33m" // Yellow
|
|
||||||
BBlue = "\033[1;34m" // Blue
|
|
||||||
BPurple = "\033[1;35m" // Purple
|
|
||||||
BCyan = "\033[1;36m" // Cyan
|
|
||||||
BWhite = "\033[1;37m" // White
|
|
||||||
|
|
||||||
// Underline
|
|
||||||
UBlack = "\033[4;30m" // Black
|
|
||||||
URed = "\033[4;31m" // Red
|
|
||||||
UGreen = "\033[4;32m" // Green
|
|
||||||
UYellow = "\033[4;33m" // Yellow
|
|
||||||
UBlue = "\033[4;34m" // Blue
|
|
||||||
UPurple = "\033[4;35m" // Purple
|
|
||||||
UCyan = "\033[4;36m" // Cyan
|
|
||||||
UWhite = "\033[4;37m" // White
|
|
||||||
|
|
||||||
// Background
|
|
||||||
On_Black = "\033[40m" // Black
|
|
||||||
On_Red = "\033[41m" // Red
|
|
||||||
On_Green = "\033[42m" // Green
|
|
||||||
On_Yellow = "\033[43m" // Yellow
|
|
||||||
On_Blue = "\033[44m" // Blue
|
|
||||||
On_Purple = "\033[45m" // Purple
|
|
||||||
On_Cyan = "\033[46m" // Cyan
|
|
||||||
On_White = "\033[47m" // White
|
|
||||||
|
|
||||||
// High Intensty
|
|
||||||
IBlack = "\033[0;90m" // Black
|
|
||||||
IRed = "\033[0;91m" // Red
|
|
||||||
IGreen = "\033[0;92m" // Green
|
|
||||||
IYellow = "\033[0;93m" // Yellow
|
|
||||||
IBlue = "\033[0;94m" // Blue
|
|
||||||
IPurple = "\033[0;95m" // Purple
|
|
||||||
ICyan = "\033[0;96m" // Cyan
|
|
||||||
IWhite = "\033[0;97m" // White
|
|
||||||
|
|
||||||
// Bold High Intensty
|
|
||||||
BIBlack = "\033[1;90m" // Black
|
|
||||||
BIRed = "\033[1;91m" // Red
|
|
||||||
BIGreen = "\033[1;92m" // Green
|
|
||||||
BIYellow = "\033[1;93m" // Yellow
|
|
||||||
BIBlue = "\033[1;94m" // Blue
|
|
||||||
BIPurple = "\033[1;95m" // Purple
|
|
||||||
BICyan = "\033[1;96m" // Cyan
|
|
||||||
BIWhite = "\033[1;97m" // White
|
|
||||||
|
|
||||||
// High Intensty backgrounds
|
|
||||||
On_IBlack = "\033[0;100m" // Black
|
|
||||||
On_IRed = "\033[0;101m" // Red
|
|
||||||
On_IGreen = "\033[0;102m" // Green
|
|
||||||
On_IYellow = "\033[0;103m" // Yellow
|
|
||||||
On_IBlue = "\033[0;104m" // Blue
|
|
||||||
On_IPurple = "\033[10;95m" // Purple
|
|
||||||
On_ICyan = "\033[0;106m" // Cyan
|
|
||||||
On_IWhite = "\033[0;107m" // White
|
|
||||||
)
|
|
||||||
|
|
||||||
// The acceptable range is [16, 231] but here we remove some very dark colors
|
|
||||||
// That make text unreadable on a dark terminal
|
|
||||||
// See https://www.hackitu.de/termcolor256/
|
|
||||||
var colors = []int{22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 57, 62, 63, 64, 65, 67, 68, 69, 70, 71, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 148, 149, 150, 151, 152, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 184, 185, 186, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 226, 227, 228, 229, 230}
|
|
||||||
var colorsIndex int = -1
|
|
||||||
var shuffled bool
|
|
||||||
|
|
||||||
func GenerateRandomAnsiColor() string {
|
|
||||||
if !shuffled {
|
|
||||||
rand.Shuffle(len(colors), func(i int, j int) {
|
|
||||||
colors[i], colors[j] = colors[j], colors[i]
|
|
||||||
})
|
|
||||||
shuffled = true
|
|
||||||
}
|
|
||||||
colorsIndex++
|
|
||||||
return fmt.Sprintf("\033[1;4;38;5;%dm", colors[colorsIndex%len(colors)])
|
|
||||||
}
|
|
@@ -1,2 +1 @@
|
|||||||
GOOS=windows GOARCH=amd64 go build -o main.exe main && cp main.exe "/c/Program Files/Git/usr/bin/cln.exe"
|
go build main && cp main.exe "/c/Program Files/Git/usr/bin/cln.exe"
|
||||||
GOOS=linux GOARCH=amd64 go build -o main_linux main
|
|
286
instruction.go
286
instruction.go
@@ -1,154 +1,132 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"regexp"
|
||||||
"regexp"
|
"strconv"
|
||||||
"strconv"
|
"strings"
|
||||||
"strings"
|
)
|
||||||
)
|
|
||||||
|
type LinkInstruction struct {
|
||||||
type LinkInstruction struct {
|
Source string
|
||||||
Source string
|
Target string
|
||||||
Target string
|
Force bool
|
||||||
Force bool
|
}
|
||||||
Hard bool
|
|
||||||
Delete bool
|
func (instruction *LinkInstruction) String() string {
|
||||||
}
|
return fmt.Sprintf("%s%s%s%s%s%s%s%s%s", SourceColor, instruction.Source, DefaultColor, deliminer, TargetColor, instruction.Target, DefaultColor, deliminer, strconv.FormatBool(instruction.Force))
|
||||||
|
}
|
||||||
func (instruction *LinkInstruction) Tidy() {
|
|
||||||
instruction.Source = strings.ReplaceAll(instruction.Source, "\"", "")
|
func ParseInstruction(line string) (LinkInstruction, error) {
|
||||||
instruction.Source = strings.ReplaceAll(instruction.Source, "\\", "/")
|
parts := strings.Split(line, deliminer)
|
||||||
instruction.Source = strings.TrimSpace(instruction.Source)
|
instruction := LinkInstruction{}
|
||||||
|
|
||||||
instruction.Target = strings.ReplaceAll(instruction.Target, "\"", "")
|
if len(parts) < 2 {
|
||||||
instruction.Target = strings.ReplaceAll(instruction.Target, "\\", "/")
|
return instruction, fmt.Errorf("invalid format - not enough parameters (must have at least source and target)")
|
||||||
instruction.Target = strings.TrimSpace(instruction.Target)
|
}
|
||||||
}
|
|
||||||
|
instruction.Source = parts[0]
|
||||||
func (instruction *LinkInstruction) String() string {
|
instruction.Target = parts[1]
|
||||||
return fmt.Sprintf("%s%s%s%s%s%s%s%s%s%s%s%s%s", SourceColor, instruction.Source, DefaultColor, deliminer, TargetColor, instruction.Target, DefaultColor, deliminer, strconv.FormatBool(instruction.Force), deliminer, strconv.FormatBool(instruction.Hard), deliminer, strconv.FormatBool(instruction.Delete))
|
instruction.Force = false
|
||||||
}
|
if len(parts) > 2 {
|
||||||
|
res, _ := regexp.MatchString("^(?i)T|TRUE$", parts[2])
|
||||||
func ParseInstruction(line, workdir string) (LinkInstruction, error) {
|
instruction.Force = res
|
||||||
line = strings.TrimSpace(line)
|
}
|
||||||
parts := strings.Split(line, deliminer)
|
|
||||||
instruction := LinkInstruction{}
|
instruction.Source, _ = ConvertHome(instruction.Source)
|
||||||
|
instruction.Target, _ = ConvertHome(instruction.Target)
|
||||||
if len(parts) < 2 {
|
|
||||||
return instruction, fmt.Errorf("invalid format - not enough parameters (must have at least source and target)")
|
instruction.Source = NormalizePath(instruction.Source)
|
||||||
}
|
instruction.Target = NormalizePath(instruction.Target)
|
||||||
|
|
||||||
instruction.Source = strings.TrimSpace(parts[0])
|
return instruction, nil
|
||||||
instruction.Target = strings.TrimSpace(parts[1])
|
}
|
||||||
instruction.Force = false
|
|
||||||
if len(parts) > 2 {
|
func (instruction *LinkInstruction) RunSync() error {
|
||||||
res, _ := regexp.MatchString(`^(?i)\s*T|TRUE\s*$`, parts[2])
|
if !FileExists(instruction.Source) {
|
||||||
instruction.Force = res
|
return fmt.Errorf("instruction source %s%s%s does not exist", SourceColor, instruction.Source, DefaultColor)
|
||||||
}
|
}
|
||||||
if len(parts) > 3 {
|
|
||||||
res, _ := regexp.MatchString(`^(?i)\s*T|TRUE\s*$`, parts[3])
|
if AreSame(instruction.Source, instruction.Target) {
|
||||||
instruction.Hard = res
|
log.Printf("Source %s%s%s and target %s%s%s are the same, %snothing to do...%s", SourceColor, instruction.Source, DefaultColor, TargetColor, instruction.Target, DefaultColor, PathColor, DefaultColor)
|
||||||
}
|
return nil
|
||||||
if len(parts) > 4 {
|
}
|
||||||
res, _ := regexp.MatchString(`^(?i)\s*T|TRUE\s*$`, parts[4])
|
|
||||||
instruction.Delete = res
|
if FileExists(instruction.Target) {
|
||||||
instruction.Force = res
|
if instruction.Force {
|
||||||
}
|
isSymlink, err := IsSymlink(instruction.Target)
|
||||||
|
if err != nil {
|
||||||
instruction.Tidy()
|
return fmt.Errorf("could not determine whether %s%s%s is a sym link or not, stopping; err: %s%+v%s", TargetColor, instruction.Target, DefaultColor, ErrorColor, err, DefaultColor)
|
||||||
instruction.Source, _ = ConvertHome(instruction.Source)
|
}
|
||||||
instruction.Target, _ = ConvertHome(instruction.Target)
|
|
||||||
|
if isSymlink {
|
||||||
instruction.Source = NormalizePath(instruction.Source, workdir)
|
log.Printf("Removing symlink at %s%s%s", TargetColor, instruction.Target, DefaultColor)
|
||||||
instruction.Target = NormalizePath(instruction.Target, workdir)
|
err = os.Remove(instruction.Target)
|
||||||
|
if err != nil {
|
||||||
return instruction, nil
|
return fmt.Errorf("failed deleting %s%s%s due to %s%+v%s", TargetColor, instruction.Target, DefaultColor, ErrorColor, err, DefaultColor)
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
func (instruction *LinkInstruction) RunAsync(status chan (error)) {
|
return fmt.Errorf("refusing to delte actual (non symlink) file %s%s%s", TargetColor, instruction.Target, DefaultColor)
|
||||||
defer close(status)
|
}
|
||||||
if !FileExists(instruction.Source) {
|
} else {
|
||||||
status <- fmt.Errorf("instruction source %s%s%s does not exist", SourceColor, instruction.Source, DefaultColor)
|
return fmt.Errorf("target %s%s%s exists - handle manually or set the 'forced' flag (3rd field)", TargetColor, instruction.Target, DefaultColor)
|
||||||
return
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !instruction.Force && AreSame(instruction.Source, instruction.Target) {
|
err := os.Symlink(instruction.Source, instruction.Target)
|
||||||
status <- fmt.Errorf("source %s%s%s and target %s%s%s are the same, %snothing to do...%s", SourceColor, instruction.Source, DefaultColor, TargetColor, instruction.Target, DefaultColor, PathColor, DefaultColor)
|
if err != nil {
|
||||||
return
|
return fmt.Errorf("failed creating symlink between %s%s%s and %s%s%s with error %s%+v%s", SourceColor, instruction.Source, DefaultColor, TargetColor, instruction.Target, DefaultColor, ErrorColor, err, DefaultColor)
|
||||||
}
|
}
|
||||||
|
log.Printf("Created symlink between %s%s%s and %s%s%s", SourceColor, instruction.Source, DefaultColor, TargetColor, instruction.Target, DefaultColor)
|
||||||
if FileExists(instruction.Target) {
|
|
||||||
if instruction.Force {
|
return nil
|
||||||
isSymlink, err := IsSymlink(instruction.Target)
|
}
|
||||||
if err != nil {
|
|
||||||
status <- fmt.Errorf("could not determine whether %s%s%s is a sym link or not, stopping; err: %s%+v%s", TargetColor, instruction.Target, DefaultColor, ErrorColor, err, DefaultColor)
|
func (instruction *LinkInstruction) RunAsync(status chan (error)) {
|
||||||
return
|
defer close(status)
|
||||||
}
|
if !FileExists(instruction.Source) {
|
||||||
|
status <- fmt.Errorf("instruction source %s%s%s does not exist", SourceColor, instruction.Source, DefaultColor)
|
||||||
if instruction.Hard {
|
return
|
||||||
info, err := os.Stat(instruction.Target)
|
}
|
||||||
if err != nil {
|
|
||||||
status <- fmt.Errorf("could not stat %s%s%s, stopping; err: %s%+v%s", TargetColor, instruction.Target, DefaultColor, ErrorColor, err, DefaultColor)
|
if AreSame(instruction.Source, instruction.Target) {
|
||||||
return
|
status <- fmt.Errorf("source %s%s%s and target %s%s%s are the same, %snothing to do...%s", SourceColor, instruction.Source, DefaultColor, TargetColor, instruction.Target, DefaultColor, PathColor, DefaultColor)
|
||||||
}
|
return
|
||||||
if info.Mode().IsRegular() && info.Name() == filepath.Base(instruction.Source) {
|
}
|
||||||
log.Printf("Overwriting existing file %s%s%s", TargetColor, instruction.Target, DefaultColor)
|
|
||||||
err := os.Remove(instruction.Target)
|
if FileExists(instruction.Target) {
|
||||||
if err != nil {
|
if instruction.Force {
|
||||||
status <- fmt.Errorf("could not remove existing file %s%s%s; err: %s%+v%s", TargetColor, instruction.Target, DefaultColor, ErrorColor, err, DefaultColor)
|
isSymlink, err := IsSymlink(instruction.Target)
|
||||||
return
|
if err != nil {
|
||||||
}
|
status <- fmt.Errorf("could not determine whether %s%s%s is a sym link or not, stopping; err: %s%+v%s", TargetColor, instruction.Target, DefaultColor, ErrorColor, err, DefaultColor)
|
||||||
}
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if isSymlink {
|
if isSymlink {
|
||||||
log.Printf("Removing symlink at %s%s%s", TargetColor, instruction.Target, DefaultColor)
|
log.Printf("Removing symlink at %s%s%s", TargetColor, instruction.Target, DefaultColor)
|
||||||
err = os.Remove(instruction.Target)
|
err = os.Remove(instruction.Target)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
status <- fmt.Errorf("failed deleting %s%s%s due to %s%+v%s", TargetColor, instruction.Target, DefaultColor, ErrorColor, err, DefaultColor)
|
status <- fmt.Errorf("failed deleting %s%s%s due to %s%+v%s", TargetColor, instruction.Target, DefaultColor, ErrorColor, err, DefaultColor)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if !instruction.Delete {
|
status <- fmt.Errorf("refusing to delte actual (non symlink) file %s%s%s", TargetColor, instruction.Target, DefaultColor)
|
||||||
status <- fmt.Errorf("refusing to delte actual (non symlink) file %s%s%s", TargetColor, instruction.Target, DefaultColor)
|
return
|
||||||
return
|
}
|
||||||
}
|
} else {
|
||||||
log.Printf("%sDeleting (!!!)%s %s%s%s", ImportantColor, DefaultColor, TargetColor, instruction.Target, DefaultColor)
|
status <- fmt.Errorf("target %s%s%s exists - handle manually or set the 'forced' flag (3rd field)", TargetColor, instruction.Target, DefaultColor)
|
||||||
err = os.RemoveAll(instruction.Target)
|
return
|
||||||
if err != nil {
|
}
|
||||||
status <- fmt.Errorf("failed deleting %s%s%s due to %s%+v%s", TargetColor, instruction.Target, DefaultColor, ErrorColor, err, DefaultColor)
|
}
|
||||||
return
|
|
||||||
}
|
err := os.Symlink(instruction.Source, instruction.Target)
|
||||||
}
|
if err != nil {
|
||||||
} else {
|
status <- fmt.Errorf("failed creating symlink between %s%s%s and %s%s%s with error %s%+v%s", SourceColor, instruction.Source, DefaultColor, TargetColor, instruction.Target, DefaultColor, ErrorColor, err, DefaultColor)
|
||||||
status <- fmt.Errorf("target %s%s%s exists - handle manually or set the 'forced' flag (3rd field)", TargetColor, instruction.Target, DefaultColor)
|
return
|
||||||
return
|
}
|
||||||
}
|
log.Printf("Created symlink between %s%s%s and %s%s%s", SourceColor, instruction.Source, DefaultColor, TargetColor, instruction.Target, DefaultColor)
|
||||||
}
|
|
||||||
|
status <- nil
|
||||||
targetDir := filepath.Dir(instruction.Target)
|
}
|
||||||
if _, err := os.Stat(targetDir); os.IsNotExist(err) {
|
|
||||||
err = os.MkdirAll(targetDir, 0755)
|
|
||||||
if err != nil {
|
|
||||||
status <- fmt.Errorf("failed creating directory %s%s%s due to %s%+v%s", TargetColor, targetDir, DefaultColor, ErrorColor, err, DefaultColor)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var err error
|
|
||||||
if instruction.Hard {
|
|
||||||
err = os.Link(instruction.Source, instruction.Target)
|
|
||||||
} else {
|
|
||||||
err = os.Symlink(instruction.Source, instruction.Target)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
status <- fmt.Errorf("failed creating symlink between %s%s%s and %s%s%s with error %s%+v%s", SourceColor, instruction.Source, DefaultColor, TargetColor, instruction.Target, DefaultColor, ErrorColor, err, DefaultColor)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
log.Printf("Created symlink between %s%s%s and %s%s%s", SourceColor, instruction.Source, DefaultColor, TargetColor, instruction.Target, DefaultColor)
|
|
||||||
|
|
||||||
status <- nil
|
|
||||||
}
|
|
490
main.go
490
main.go
@@ -1,252 +1,238 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"flag"
|
"flag"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"regexp"
|
||||||
"regexp"
|
"sync"
|
||||||
"sync"
|
"sync/atomic"
|
||||||
"sync/atomic"
|
)
|
||||||
)
|
|
||||||
|
const deliminer = ","
|
||||||
const deliminer = ","
|
const (
|
||||||
const SourceColor = Purple
|
Black = "\033[30m"
|
||||||
const TargetColor = Yellow
|
Red = "\033[31m"
|
||||||
const ErrorColor = URed
|
Green = "\033[32m"
|
||||||
const ImportantColor = BRed
|
Yellow = "\033[33m"
|
||||||
const DefaultColor = White
|
Blue = "\033[34m"
|
||||||
const PathColor = Green
|
Magenta = "\033[35m"
|
||||||
|
Cyan = "\033[36m"
|
||||||
var DirRegex, _ = regexp.Compile(`^(.+?)[/\\]sync$`)
|
White = "\033[37m"
|
||||||
var FileRegex, _ = regexp.Compile(`^sync$`)
|
)
|
||||||
var programName = os.Args[0]
|
const SourceColor = Magenta
|
||||||
|
const TargetColor = Yellow
|
||||||
func main() {
|
const ErrorColor = Red
|
||||||
recurse := flag.String("r", "", "recurse into directories")
|
const DefaultColor = White
|
||||||
file := flag.String("f", "", "file to read instructions from")
|
const PathColor = Green
|
||||||
debug := flag.Bool("d", false, "debug")
|
|
||||||
flag.Parse()
|
var DirRegex, _ = regexp.Compile(`^(.+?)[/\\]sync$`)
|
||||||
|
var FileRegex, _ = regexp.Compile(`^sync$`)
|
||||||
if *debug {
|
var programName = os.Args[0]
|
||||||
log.SetFlags(log.Lmicroseconds | log.Lshortfile)
|
|
||||||
logFile, err := os.Create("main.log")
|
func main() {
|
||||||
if err != nil {
|
recurse := flag.String("r", "", "recurse into directories")
|
||||||
log.Printf("Error creating log file: %v", err)
|
file := flag.String("f", "", "file to read instructions from")
|
||||||
os.Exit(1)
|
debug := flag.Bool("d", false, "debug")
|
||||||
}
|
flag.Parse()
|
||||||
logger := io.MultiWriter(os.Stdout, logFile)
|
|
||||||
log.SetOutput(logger)
|
if *debug {
|
||||||
} else {
|
log.SetFlags(log.Lmicroseconds | log.Lshortfile)
|
||||||
log.SetFlags(log.Lmicroseconds)
|
logFile, err := os.Create("main.log")
|
||||||
}
|
if err != nil {
|
||||||
|
log.Printf("Error creating log file: %v", err)
|
||||||
instructions := make(chan *LinkInstruction, 1000)
|
os.Exit(1)
|
||||||
status := make(chan error)
|
}
|
||||||
|
logger := io.MultiWriter(os.Stdout, logFile)
|
||||||
// Check input sources in priority order
|
log.SetOutput(logger)
|
||||||
switch {
|
} else {
|
||||||
case *recurse != "":
|
log.SetFlags(log.Lmicroseconds)
|
||||||
log.Printf("Recurse: %s", *recurse)
|
}
|
||||||
go ReadFromFilesRecursively(*recurse, instructions, status)
|
|
||||||
|
log.Printf("Recurse: %s", *recurse)
|
||||||
case *file != "":
|
log.Printf("File: %s", *file)
|
||||||
log.Printf("File: %s", *file)
|
|
||||||
go ReadFromFile(*file, instructions, status, true)
|
instructions := make(chan LinkInstruction, 1000)
|
||||||
|
status := make(chan error)
|
||||||
case len(flag.Args()) > 0:
|
if *recurse != "" {
|
||||||
log.Printf("Reading from command line arguments")
|
go ReadFromFilesRecursively(*recurse, instructions, status)
|
||||||
go ReadFromArgs(instructions, status)
|
} else if *file != "" {
|
||||||
|
go ReadFromFile(*file, instructions, status, true)
|
||||||
case IsPipeInput():
|
} else if len(os.Args) > 1 {
|
||||||
log.Printf("Reading from stdin pipe")
|
go ReadFromArgs(instructions, status)
|
||||||
go ReadFromStdin(instructions, status)
|
} else {
|
||||||
|
go ReadFromStdin(instructions, status)
|
||||||
default:
|
}
|
||||||
if _, err := os.Stat("sync"); err == nil {
|
|
||||||
log.Printf("Using default sync file")
|
go func() {
|
||||||
go ReadFromFile("sync", instructions, status, true)
|
for {
|
||||||
} else {
|
err, ok := <-status
|
||||||
log.Printf("No input provided")
|
if !ok {
|
||||||
log.Printf("Provide input as: ")
|
break
|
||||||
log.Printf("Arguments - %s <source>,<target>,<force?>", programName)
|
}
|
||||||
log.Printf("File - %s -f <file>", programName)
|
if err != nil {
|
||||||
log.Printf("Folder (finding sync files in folder recursively) - %s -r <folder>", programName)
|
log.Println(err)
|
||||||
log.Printf("stdin - (cat <file> | %s)", programName)
|
}
|
||||||
os.Exit(1)
|
}
|
||||||
}
|
}()
|
||||||
}
|
|
||||||
|
var instructionsDone int32
|
||||||
go func() {
|
var wg sync.WaitGroup
|
||||||
for {
|
for {
|
||||||
err, ok := <-status
|
instruction, ok := <-instructions
|
||||||
if !ok {
|
if !ok {
|
||||||
break
|
log.Printf("No more instructions to process")
|
||||||
}
|
break
|
||||||
if err != nil {
|
}
|
||||||
log.Println(err)
|
log.Printf("Processing: %s", instruction.String())
|
||||||
}
|
status := make(chan error)
|
||||||
}
|
go instruction.RunAsync(status)
|
||||||
}()
|
wg.Add(1)
|
||||||
|
err := <-status
|
||||||
var instructionsDone int32
|
if err != nil {
|
||||||
var wg sync.WaitGroup
|
log.Printf("Failed parsing instruction %s%s%s due to %s%+v%s", SourceColor, instruction.String(), DefaultColor, ErrorColor, err, DefaultColor)
|
||||||
for {
|
}
|
||||||
instruction, ok := <-instructions
|
atomic.AddInt32(&instructionsDone, 1)
|
||||||
if !ok {
|
wg.Done()
|
||||||
log.Printf("No more instructions to process")
|
}
|
||||||
break
|
wg.Wait()
|
||||||
}
|
log.Println("All done")
|
||||||
log.Printf("Processing: %s", instruction.String())
|
if instructionsDone == 0 {
|
||||||
status := make(chan error)
|
log.Printf("No input provided")
|
||||||
go instruction.RunAsync(status)
|
log.Printf("Provide input as: ")
|
||||||
wg.Add(1)
|
log.Printf("Arguments - %s <source>,<target>,<force?>", programName)
|
||||||
err := <-status
|
log.Printf("File - %s -f <file>", programName)
|
||||||
if err != nil {
|
log.Printf("Folder (finding sync files in folder recursively) - %s -r <folder>", programName)
|
||||||
log.Printf("Failed parsing instruction %s%s%s due to %s%+v%s", SourceColor, instruction.String(), DefaultColor, ErrorColor, err, DefaultColor)
|
log.Printf("stdin - (cat <file> | %s)", programName)
|
||||||
}
|
os.Exit(1)
|
||||||
atomic.AddInt32(&instructionsDone, 1)
|
}
|
||||||
wg.Done()
|
}
|
||||||
}
|
|
||||||
wg.Wait()
|
func ReadFromFilesRecursively(input string, output chan LinkInstruction, status chan error) {
|
||||||
log.Println("All done")
|
defer close(output)
|
||||||
if instructionsDone == 0 {
|
defer close(status)
|
||||||
log.Printf("No instructions were processed")
|
|
||||||
os.Exit(1)
|
input = NormalizePath(input)
|
||||||
}
|
log.Printf("Reading input from files recursively starting in %s%s%s", PathColor, input, DefaultColor)
|
||||||
}
|
|
||||||
|
files := make(chan string, 128)
|
||||||
func IsPipeInput() bool {
|
recurseStatus := make(chan error)
|
||||||
info, err := os.Stdin.Stat()
|
go GetSyncFilesRecursively(input, files, recurseStatus)
|
||||||
if err != nil {
|
go func() {
|
||||||
return false
|
for {
|
||||||
}
|
err, ok := <-recurseStatus
|
||||||
return info.Mode()&os.ModeNamedPipe != 0
|
if !ok {
|
||||||
}
|
break
|
||||||
|
}
|
||||||
func ReadFromFilesRecursively(input string, output chan *LinkInstruction, status chan error) {
|
if err != nil {
|
||||||
defer close(output)
|
log.Printf("Failed to get sync files recursively: %s%+v%s", ErrorColor, err, DefaultColor)
|
||||||
defer close(status)
|
status <- err
|
||||||
|
}
|
||||||
workdir, _ := os.Getwd()
|
}
|
||||||
input = NormalizePath(input, workdir)
|
}()
|
||||||
log.Printf("Reading input from files recursively starting in %s%s%s", PathColor, input, DefaultColor)
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
files := make(chan string, 128)
|
for {
|
||||||
recurseStatus := make(chan error)
|
file, ok := <-files
|
||||||
go GetSyncFilesRecursively(input, files, recurseStatus)
|
if !ok {
|
||||||
go func() {
|
log.Printf("No more files to process")
|
||||||
for {
|
break
|
||||||
err, ok := <-recurseStatus
|
}
|
||||||
if !ok {
|
wg.Add(1)
|
||||||
break
|
go func() {
|
||||||
}
|
defer wg.Done()
|
||||||
if err != nil {
|
log.Println(file)
|
||||||
log.Printf("Failed to get sync files recursively: %s%+v%s", ErrorColor, err, DefaultColor)
|
file = NormalizePath(file)
|
||||||
status <- err
|
log.Printf("Processing file: %s%s%s", PathColor, file, DefaultColor)
|
||||||
}
|
|
||||||
}
|
// This "has" to be done because instructions are resolved in relation to cwd
|
||||||
}()
|
fileDir := DirRegex.FindStringSubmatch(file)
|
||||||
|
if fileDir == nil {
|
||||||
var wg sync.WaitGroup
|
log.Printf("Failed to extract directory from %s%s%s", SourceColor, file, DefaultColor)
|
||||||
for {
|
return
|
||||||
file, ok := <-files
|
}
|
||||||
if !ok {
|
log.Printf("Changing directory to %s%s%s (for %s%s%s)", PathColor, fileDir[1], DefaultColor, PathColor, file, DefaultColor)
|
||||||
log.Printf("No more files to process")
|
err := os.Chdir(fileDir[1])
|
||||||
break
|
if err != nil {
|
||||||
}
|
log.Printf("Failed to change directory to %s%s%s: %s%+v%s", SourceColor, fileDir[1], DefaultColor, ErrorColor, err, DefaultColor)
|
||||||
wg.Add(1)
|
return
|
||||||
go func() {
|
}
|
||||||
defer wg.Done()
|
|
||||||
log.Println(file)
|
ReadFromFile(file, output, status, false)
|
||||||
file = NormalizePath(file, workdir)
|
}()
|
||||||
log.Printf("Processing file: %s%s%s", PathColor, file, DefaultColor)
|
}
|
||||||
|
wg.Wait()
|
||||||
// This "has" to be done because instructions are resolved in relation to cwd
|
}
|
||||||
fileDir := DirRegex.FindStringSubmatch(file)
|
func ReadFromFile(input string, output chan LinkInstruction, status chan error, doclose bool) {
|
||||||
if fileDir == nil {
|
if doclose {
|
||||||
log.Printf("Failed to extract directory from %s%s%s", SourceColor, file, DefaultColor)
|
defer close(output)
|
||||||
return
|
defer close(status)
|
||||||
}
|
}
|
||||||
log.Printf("Changing directory to %s%s%s (for %s%s%s)", PathColor, fileDir[1], DefaultColor, PathColor, file, DefaultColor)
|
|
||||||
err := os.Chdir(fileDir[1])
|
input = NormalizePath(input)
|
||||||
if err != nil {
|
log.Printf("Reading input from file: %s%s%s", PathColor, input, DefaultColor)
|
||||||
log.Printf("Failed to change directory to %s%s%s: %s%+v%s", SourceColor, fileDir[1], DefaultColor, ErrorColor, err, DefaultColor)
|
file, err := os.Open(input)
|
||||||
return
|
if err != nil {
|
||||||
}
|
log.Fatalf("Failed to open file %s%s%s: %s%+v%s", SourceColor, input, DefaultColor, ErrorColor, err, DefaultColor)
|
||||||
|
return
|
||||||
ReadFromFile(file, output, status, false)
|
}
|
||||||
}()
|
defer file.Close()
|
||||||
}
|
|
||||||
wg.Wait()
|
scanner := bufio.NewScanner(file)
|
||||||
}
|
for scanner.Scan() {
|
||||||
|
line := scanner.Text()
|
||||||
func ReadFromFile(input string, output chan *LinkInstruction, status chan error, doclose bool) {
|
instruction, err := ParseInstruction(line)
|
||||||
if doclose {
|
if err != nil {
|
||||||
defer close(output)
|
log.Printf("Error parsing line: %s'%s'%s, error: %s%+v%s", SourceColor, line, DefaultColor, ErrorColor, err, DefaultColor)
|
||||||
defer close(status)
|
continue
|
||||||
}
|
}
|
||||||
|
log.Printf("Read instruction: %s", instruction.String())
|
||||||
input = NormalizePath(input, filepath.Dir(input))
|
output <- instruction
|
||||||
log.Printf("Reading input from file: %s%s%s", PathColor, input, DefaultColor)
|
}
|
||||||
file, err := os.Open(input)
|
}
|
||||||
if err != nil {
|
func ReadFromArgs(output chan LinkInstruction, status chan error) {
|
||||||
log.Fatalf("Failed to open file %s%s%s: %s%+v%s", SourceColor, input, DefaultColor, ErrorColor, err, DefaultColor)
|
defer close(output)
|
||||||
return
|
defer close(status)
|
||||||
}
|
|
||||||
defer file.Close()
|
log.Printf("Reading input from args")
|
||||||
|
for _, arg := range os.Args[1:] {
|
||||||
scanner := bufio.NewScanner(file)
|
instruction, err := ParseInstruction(arg)
|
||||||
for scanner.Scan() {
|
if err != nil {
|
||||||
line := scanner.Text()
|
log.Printf("Error parsing arg: %s'%s'%s, error: %s%+v%s", SourceColor, arg, DefaultColor, ErrorColor, err, DefaultColor)
|
||||||
instruction, err := ParseInstruction(line, filepath.Dir(input))
|
continue
|
||||||
if err != nil {
|
}
|
||||||
log.Printf("Error parsing line: %s'%s'%s, error: %s%+v%s", SourceColor, line, DefaultColor, ErrorColor, err, DefaultColor)
|
output <- instruction
|
||||||
continue
|
}
|
||||||
}
|
}
|
||||||
log.Printf("Read instruction: %s", instruction.String())
|
func ReadFromStdin(output chan LinkInstruction, status chan error) {
|
||||||
output <- &instruction
|
defer close(output)
|
||||||
}
|
defer close(status)
|
||||||
}
|
|
||||||
|
log.Printf("Reading input from stdin")
|
||||||
func ReadFromArgs(output chan *LinkInstruction, status chan error) {
|
|
||||||
defer close(output)
|
info, err := os.Stdin.Stat()
|
||||||
defer close(status)
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to stat stdin: %s%+v%s", ErrorColor, err, DefaultColor)
|
||||||
workdir, _ := os.Getwd()
|
status <- err
|
||||||
log.Printf("Reading input from args")
|
return
|
||||||
for _, arg := range flag.Args() {
|
}
|
||||||
instruction, err := ParseInstruction(arg, workdir)
|
if info.Mode()&os.ModeNamedPipe != 0 {
|
||||||
if err != nil {
|
scanner := bufio.NewScanner(os.Stdin)
|
||||||
log.Printf("Error parsing arg: %s'%s'%s, error: %s%+v%s", SourceColor, arg, DefaultColor, ErrorColor, err, DefaultColor)
|
for scanner.Scan() {
|
||||||
continue
|
line := scanner.Text()
|
||||||
}
|
instruction, err := ParseInstruction(line)
|
||||||
output <- &instruction
|
if err != nil {
|
||||||
}
|
log.Printf("Error parsing line: %s'%s'%s, error: %s%+v%s", SourceColor, line, DefaultColor, ErrorColor, err, DefaultColor)
|
||||||
}
|
continue
|
||||||
|
}
|
||||||
func ReadFromStdin(output chan *LinkInstruction, status chan error) {
|
output <- instruction
|
||||||
defer close(output)
|
}
|
||||||
defer close(status)
|
if err := scanner.Err(); err != nil {
|
||||||
|
log.Fatalf("Error reading from stdin: %s%+v%s", ErrorColor, err, DefaultColor)
|
||||||
workdir, _ := os.Getwd()
|
status <- err
|
||||||
log.Printf("Reading input from stdin")
|
return
|
||||||
|
}
|
||||||
scanner := bufio.NewScanner(os.Stdin)
|
}
|
||||||
for scanner.Scan() {
|
}
|
||||||
line := scanner.Text()
|
|
||||||
instruction, err := ParseInstruction(line, workdir)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Error parsing line: %s'%s'%s, error: %s%+v%s", SourceColor, line, DefaultColor, ErrorColor, err, DefaultColor)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
output <- &instruction
|
|
||||||
}
|
|
||||||
if err := scanner.Err(); err != nil {
|
|
||||||
log.Fatalf("Error reading from stdin: %s%+v%s", ErrorColor, err, DefaultColor)
|
|
||||||
status <- err
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
291
util.go
291
util.go
@@ -1,152 +1,139 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func IsSymlink(path string) (bool, error) {
|
func IsSymlink(path string) (bool, error) {
|
||||||
fileInfo, err := os.Lstat(path)
|
fileInfo, err := os.Lstat(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// os.ModeSymlink is a bitmask that identifies the symlink mode.
|
// os.ModeSymlink is a bitmask that identifies the symlink mode.
|
||||||
// If the file mode & os.ModeSymlink is non-zero, the file is a symlink.
|
// If the file mode & os.ModeSymlink is non-zero, the file is a symlink.
|
||||||
return fileInfo.Mode()&os.ModeSymlink != 0, nil
|
return fileInfo.Mode()&os.ModeSymlink != 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func FileExists(path string) bool {
|
func FileExists(path string) bool {
|
||||||
_, err := os.Lstat(path)
|
_, err := os.Lstat(path)
|
||||||
return err == nil
|
return err == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NormalizePath(input, workdir string) string {
|
func NormalizePath(input string) string {
|
||||||
input = filepath.Clean(input)
|
workingdirectory, _ := os.Getwd()
|
||||||
input = filepath.ToSlash(input)
|
input = strings.ReplaceAll(input, "\\", "/")
|
||||||
input = strings.ReplaceAll(input, "\"", "")
|
input = strings.ReplaceAll(input, "\"", "")
|
||||||
|
|
||||||
if !filepath.IsAbs(input) {
|
if !filepath.IsAbs(input) {
|
||||||
log.Printf("Input '%s' is not absolute, prepending work dir '%s'", input, workdir)
|
input = workingdirectory + "/" + input
|
||||||
var err error
|
}
|
||||||
input = filepath.Join(workdir, input)
|
|
||||||
input, err = filepath.Abs(input)
|
return filepath.Clean(input)
|
||||||
if err != nil {
|
}
|
||||||
log.Printf("Failed to get absolute path for %s%s%s: %s%+v%s", SourceColor, input, DefaultColor, ErrorColor, err, DefaultColor)
|
|
||||||
return input
|
func AreSame(lhs string, rhs string) bool {
|
||||||
}
|
lhsinfo, err := os.Stat(lhs)
|
||||||
}
|
if err != nil {
|
||||||
|
return false
|
||||||
input = filepath.Clean(input)
|
}
|
||||||
input = filepath.ToSlash(input)
|
rhsinfo, err := os.Stat(rhs)
|
||||||
return input
|
if err != nil {
|
||||||
}
|
return false
|
||||||
|
}
|
||||||
func AreSame(lhs string, rhs string) bool {
|
|
||||||
lhsinfo, err := os.Stat(lhs)
|
return os.SameFile(lhsinfo, rhsinfo)
|
||||||
if err != nil {
|
}
|
||||||
return false
|
|
||||||
}
|
func ConvertHome(input string) (string, error) {
|
||||||
rhsinfo, err := os.Stat(rhs)
|
if strings.Contains(input, "~") {
|
||||||
if err != nil {
|
homedir, err := os.UserHomeDir()
|
||||||
return false
|
if err != nil {
|
||||||
}
|
return input, fmt.Errorf("unable to convert ~ to user directory with error %+v", err)
|
||||||
|
}
|
||||||
return os.SameFile(lhsinfo, rhsinfo)
|
|
||||||
}
|
return strings.Replace(input, "~", homedir, 1), nil
|
||||||
|
}
|
||||||
func ConvertHome(input string) (string, error) {
|
return input, nil
|
||||||
if strings.HasPrefix(input, "~/") {
|
}
|
||||||
homedir, err := os.UserHomeDir()
|
|
||||||
if err != nil {
|
func GetSyncFilesRecursively(input string, output chan string, status chan error) {
|
||||||
return input, fmt.Errorf("unable to convert ~ to user directory with error %+v", err)
|
defer close(output)
|
||||||
}
|
defer close(status)
|
||||||
|
|
||||||
return strings.Replace(input, "~", homedir, 1), nil
|
var filesProcessed int32
|
||||||
}
|
var foldersProcessed int32
|
||||||
return input, nil
|
progressTicker := time.NewTicker(200 * time.Millisecond)
|
||||||
}
|
defer progressTicker.Stop()
|
||||||
|
|
||||||
func GetSyncFilesRecursively(input string, output chan string, status chan error) {
|
var wg sync.WaitGroup
|
||||||
defer close(output)
|
var initial sync.Once
|
||||||
defer close(status)
|
wg.Add(1)
|
||||||
|
directories := make(chan string, 100000)
|
||||||
var filesProcessed int32
|
workerPool := make(chan struct{}, 4000)
|
||||||
var foldersProcessed int32
|
directories <- input
|
||||||
progressTicker := time.NewTicker(200 * time.Millisecond)
|
|
||||||
defer progressTicker.Stop()
|
go func() {
|
||||||
|
for {
|
||||||
var wg sync.WaitGroup
|
fmt.Printf("\rFiles processed: %d; Folders processed: %d; Workers: %d; Directory Stack Size: %d;", filesProcessed, foldersProcessed, len(workerPool), len(directories))
|
||||||
var initial sync.Once
|
<-progressTicker.C
|
||||||
var done bool
|
}
|
||||||
wg.Add(1)
|
}()
|
||||||
directories := make(chan string, 100000)
|
|
||||||
workerPool := make(chan struct{}, 4000)
|
log.Printf("%+v", len(workerPool))
|
||||||
directories <- input
|
go func() {
|
||||||
|
for directory := range directories {
|
||||||
go func() {
|
workerPool <- struct{}{}
|
||||||
for {
|
wg.Add(1)
|
||||||
fmt.Printf("\rFiles processed: %d; Folders processed: %d; Workers: %d; Directory Stack Size: %d;", atomic.LoadInt32((&filesProcessed)), atomic.LoadInt32(&foldersProcessed), len(workerPool), len(directories))
|
go func(directory string) {
|
||||||
<-progressTicker.C
|
atomic.AddInt32(&foldersProcessed, 1)
|
||||||
}
|
defer wg.Done()
|
||||||
}()
|
defer func() { <-workerPool }()
|
||||||
|
|
||||||
// log.Printf("%+v", len(workerPool))
|
files, err := os.ReadDir(directory)
|
||||||
go func() {
|
if err != nil {
|
||||||
for directory := range directories {
|
log.Printf("Error reading directory %s: %+v", directory, err)
|
||||||
workerPool <- struct{}{}
|
return
|
||||||
wg.Add(1)
|
}
|
||||||
go func(directory string) {
|
|
||||||
atomic.AddInt32(&foldersProcessed, 1)
|
for _, file := range files {
|
||||||
defer wg.Done()
|
// log.Printf("Processing file %s", file.Name())
|
||||||
defer func() { <-workerPool }()
|
if file.IsDir() {
|
||||||
|
directories <- filepath.Join(directory, file.Name())
|
||||||
files, err := os.ReadDir(directory)
|
} else {
|
||||||
if err != nil {
|
// log.Println(file.Name(), DirRegex.MatchString(file.Name()))
|
||||||
log.Printf("Error reading directory %s: %+v", directory, err)
|
if FileRegex.MatchString(file.Name()) {
|
||||||
return
|
// log.Printf("Writing")
|
||||||
}
|
output <- filepath.Join(directory, file.Name())
|
||||||
|
}
|
||||||
for _, file := range files {
|
atomic.AddInt32(&filesProcessed, 1)
|
||||||
// log.Printf("Processing file %s", file.Name())
|
}
|
||||||
if file.IsDir() {
|
}
|
||||||
directories <- filepath.Join(directory, file.Name())
|
// log.Printf("Done reading directory %s", directory)
|
||||||
} else {
|
|
||||||
// log.Println(file.Name(), DirRegex.MatchString(file.Name()))
|
initial.Do(func() {
|
||||||
if FileRegex.MatchString(file.Name()) {
|
// Parallelism is very difficult...
|
||||||
// log.Printf("Writing")
|
time.Sleep(250 * time.Millisecond)
|
||||||
output <- filepath.Join(directory, file.Name())
|
wg.Done()
|
||||||
}
|
})
|
||||||
atomic.AddInt32(&filesProcessed, 1)
|
}(directory)
|
||||||
}
|
}
|
||||||
}
|
}()
|
||||||
// log.Printf("Done reading directory %s", directory)
|
|
||||||
done = len(directories) == 0
|
// This actually does not go through ALL files sadly...
|
||||||
if done {
|
// It so happens (very often) that we manage to quit between one iteration ending
|
||||||
initial.Do(func() {
|
// And another beginning
|
||||||
wg.Done()
|
// In such a state workgroup is decreased and, before it has a chance to increase, we are done
|
||||||
})
|
// What I should do here is only terminate if directories is empty
|
||||||
}
|
// ...but how do I do that?
|
||||||
}(directory)
|
// I might be wrong... Fuck knows...
|
||||||
}
|
wg.Wait()
|
||||||
}()
|
log.Printf("Files processed: %d; Folders processed: %d", filesProcessed, foldersProcessed)
|
||||||
|
}
|
||||||
// This actually does not go through ALL files sadly...
|
|
||||||
// It so happens (very often) that we manage to quit between one iteration ending
|
|
||||||
// And another beginning
|
|
||||||
// In such a state workgroup is decreased and, before it has a chance to increase, we are done
|
|
||||||
// What I should do here is only terminate if directories is empty
|
|
||||||
// ...but how do I do that?
|
|
||||||
// I might be wrong... Fuck knows...
|
|
||||||
// It also sometimes happens that wg.Wait triggers after wg.Done on line 97 but before the next (what would be!) wg.Add on line 94
|
|
||||||
// This happens much more often with a small number of workers
|
|
||||||
// Such is the nature of race conditions...
|
|
||||||
wg.Wait()
|
|
||||||
log.Printf("Files processed: %d; Folders processed: %d", filesProcessed, foldersProcessed)
|
|
||||||
}
|
|
||||||
|
Reference in New Issue
Block a user