initial commit
This commit is contained in:
commit
213db984ab
15 changed files with 718 additions and 0 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
__debug_bin
|
||||
*.sqlite*
|
42
.vscode/launch.json
vendored
Normal file
42
.vscode/launch.json
vendored
Normal file
|
@ -0,0 +1,42 @@
|
|||
{
|
||||
"version": "0.0.1",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "gosync",
|
||||
"type": "go",
|
||||
"request": "launch",
|
||||
"mode": "auto",
|
||||
"program": "${workspaceFolder}/main.go",
|
||||
"args": [
|
||||
"/home/velvettear/downloads/music",
|
||||
"192.168.1.11:/tmp",
|
||||
"--password",
|
||||
"$Velvet90",
|
||||
"--concurrency",
|
||||
"4",
|
||||
"--verbose",
|
||||
],
|
||||
"console": "integratedTerminal"
|
||||
},
|
||||
{
|
||||
"name": "gosync-root",
|
||||
"type": "go",
|
||||
"request": "launch",
|
||||
"mode": "auto",
|
||||
"program": "${workspaceFolder}/main.go",
|
||||
"args": [
|
||||
"/home/velvettear/downloads/music",
|
||||
"192.168.1.11:/tmp",
|
||||
"--user",
|
||||
"velvettear",
|
||||
"--password",
|
||||
"$Velvet90",
|
||||
"--concurrency",
|
||||
"4",
|
||||
"--verbose",
|
||||
],
|
||||
"asRoot": true,
|
||||
"console": "integratedTerminal"
|
||||
}
|
||||
]
|
||||
}
|
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"sqlite.logLevel": "DEBUG"
|
||||
}
|
20
LICENSE.md
Normal file
20
LICENSE.md
Normal file
|
@ -0,0 +1,20 @@
|
|||
# MIT License
|
||||
**Copyright (c) 2022 Daniel Sommer \<daniel.sommer@velvettear.de\>**
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is furnished
|
||||
to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice (including the next
|
||||
paragraph) shall be included in all copies or substantial portions of the
|
||||
Software.
|
||||
|
||||
**THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
|
||||
OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
|
||||
OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.**
|
22
README.md
Normal file
22
README.md
Normal file
|
@ -0,0 +1,22 @@
|
|||
# gosync
|
||||
|
||||
a simple wrapper for concurrent rsync processes written in golang
|
||||
|
||||
## requirements
|
||||
|
||||
- [rsync](https://linux.die.net/man/1/rsync)
|
||||
- [sshpass](https://linux.die.net/man/1/sshpass)
|
||||
- [nerd fonts](https://www.nerdfonts.com)
|
||||
|
||||
## run
|
||||
|
||||
`gosync [source] [target] (options)`
|
||||
|
||||
### options
|
||||
|
||||
| short | long | description |
|
||||
| ----- | ------------- | ------------------------------------------- |
|
||||
| -u | --user | set user for ssh / rsync |
|
||||
| -p | --password | set password for ssh / rsync |
|
||||
| -c | --concurrency | set limit for concurrent rsync processes |
|
||||
| -v | --verbose | enable verbose / debug output |
|
16
go.mod
Normal file
16
go.mod
Normal file
|
@ -0,0 +1,16 @@
|
|||
module velvettear/gosync
|
||||
|
||||
go 1.21.0
|
||||
|
||||
require github.com/vbauerster/mpb/v8 v8.6.1
|
||||
|
||||
require (
|
||||
github.com/VividCortex/ewma v1.2.0 // indirect
|
||||
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect
|
||||
github.com/fatih/color v1.15.0
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.17 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.15 // indirect
|
||||
github.com/rivo/uniseg v0.4.4 // indirect
|
||||
golang.org/x/sys v0.12.0 // indirect
|
||||
)
|
21
go.sum
Normal file
21
go.sum
Normal file
|
@ -0,0 +1,21 @@
|
|||
github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow=
|
||||
github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4=
|
||||
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8=
|
||||
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo=
|
||||
github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
|
||||
github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
|
||||
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
|
||||
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
|
||||
github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/vbauerster/mpb/v8 v8.6.1 h1:XbBpIbJxJOO9yMcKPpI4oEFPW6tLAptefNQJNcGWri8=
|
||||
github.com/vbauerster/mpb/v8 v8.6.1/go.mod h1:S0tuIjikxlLxCeNijNhwAuD/BB3UE/d2nygG8SOldk0=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
BIN
gosync
Executable file
BIN
gosync
Executable file
Binary file not shown.
99
log/log.go
Normal file
99
log/log.go
Normal file
|
@ -0,0 +1,99 @@
|
|||
package log
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const LEVEL_DEBUG = 0
|
||||
const LEVEL_INFO = 1
|
||||
const LEVEL_WARNING = 2
|
||||
const LEVEL_ERROR = 3
|
||||
const LEVEL_FATAL = 4
|
||||
|
||||
var logLevel = 0
|
||||
|
||||
// exported functions
|
||||
func SetLogLevel(level int) {
|
||||
logLevel = level
|
||||
}
|
||||
|
||||
func Debug(message string, extras ...string) {
|
||||
DebugTimed(message, -1, extras...)
|
||||
}
|
||||
|
||||
func DebugTimed(message string, timestamp int64, extras ...string) {
|
||||
trace(LEVEL_DEBUG, timestamp, message, extras...)
|
||||
}
|
||||
|
||||
func Info(message string, extras ...string) {
|
||||
InfoTimed(message, -1, extras...)
|
||||
}
|
||||
|
||||
func InfoTimed(message string, timestamp int64, extras ...string) {
|
||||
trace(LEVEL_INFO, timestamp, message, extras...)
|
||||
}
|
||||
|
||||
func Warning(message string, extras ...string) {
|
||||
WarningTimed(message, -1, extras...)
|
||||
}
|
||||
|
||||
func WarningTimed(message string, timestamp int64, extras ...string) {
|
||||
trace(LEVEL_WARNING, timestamp, message, extras...)
|
||||
}
|
||||
|
||||
func Error(message string, extras ...string) {
|
||||
ErrorTimed(message, -1, extras...)
|
||||
}
|
||||
|
||||
func ErrorTimed(message string, timestamp int64, extras ...string) {
|
||||
trace(LEVEL_ERROR, -1, message, extras...)
|
||||
}
|
||||
|
||||
func Fatal(message string, extras ...string) {
|
||||
FatalTimed(message, -1, extras...)
|
||||
}
|
||||
|
||||
func FatalTimed(message string, timestamp int64, extras ...string) {
|
||||
trace(LEVEL_FATAL, timestamp, message, extras...)
|
||||
trace(LEVEL_FATAL, -1, "exiting...")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// unexported functions
|
||||
func trace(level int, timestamp int64, message string, extras ...string) {
|
||||
if len(message) == 0 || level < logLevel {
|
||||
return
|
||||
}
|
||||
suffix := strings.Join(extras, " | ")
|
||||
if len(suffix) > 0 {
|
||||
message += " (" + suffix + ")"
|
||||
}
|
||||
if timestamp >= 0 {
|
||||
|
||||
message += " [" + strconv.Itoa(int(time.Now().UnixMilli()-timestamp)) + "ms" + "]"
|
||||
}
|
||||
fmt.Println(buildLogMessage(getPrefixForLogLevel(level), message))
|
||||
}
|
||||
|
||||
func getPrefixForLogLevel(level int) string {
|
||||
switch level {
|
||||
case LEVEL_FATAL:
|
||||
return "fatal"
|
||||
case LEVEL_ERROR:
|
||||
return "error"
|
||||
case LEVEL_WARNING:
|
||||
return "warning"
|
||||
case LEVEL_INFO:
|
||||
return "info"
|
||||
default:
|
||||
return "debug"
|
||||
}
|
||||
}
|
||||
|
||||
func buildLogMessage(prefix string, message string) string {
|
||||
return "[" + prefix + "] > " + message
|
||||
}
|
26
main.go
Normal file
26
main.go
Normal file
|
@ -0,0 +1,26 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"time"
|
||||
"velvettear/gosync/log"
|
||||
"velvettear/gosync/settings"
|
||||
"velvettear/gosync/tools"
|
||||
)
|
||||
|
||||
func main() {
|
||||
timestamp := time.Now()
|
||||
settings.Initialize()
|
||||
log.Info("starting gosync...")
|
||||
error := tools.TestConnection()
|
||||
if error != nil {
|
||||
log.Fatal("encountered an error connecting to the remove target", error.Error())
|
||||
}
|
||||
tools.Transfer()
|
||||
exit(timestamp, 0)
|
||||
}
|
||||
|
||||
func exit(timestamp time.Time, code int) {
|
||||
log.InfoTimed("gosync finished - exiting...", timestamp.UnixMilli())
|
||||
os.Exit(code)
|
||||
}
|
83
settings/arguments.go
Normal file
83
settings/arguments.go
Normal file
|
@ -0,0 +1,83 @@
|
|||
package settings
|
||||
|
||||
import (
|
||||
"os"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"velvettear/gosync/log"
|
||||
)
|
||||
|
||||
// exported function(s)
|
||||
func Initialize() {
|
||||
os.Args = os.Args[1:]
|
||||
if len(os.Args) < 2 {
|
||||
log.Fatal("error: missing arguments")
|
||||
}
|
||||
var arguments []string
|
||||
for index, arg := range os.Args {
|
||||
switch strings.ToLower(arg) {
|
||||
case "-v":
|
||||
fallthrough
|
||||
case "--verbose":
|
||||
setVerbose(true)
|
||||
case "-c":
|
||||
fallthrough
|
||||
case "--concurrency":
|
||||
var concurrency int
|
||||
tmpIndex := index + 1
|
||||
if tmpIndex < len(os.Args) {
|
||||
tmp, error := strconv.Atoi(os.Args[tmpIndex])
|
||||
if error == nil {
|
||||
concurrency = tmp
|
||||
}
|
||||
}
|
||||
if concurrency == 0 {
|
||||
concurrency = runtime.NumCPU()
|
||||
}
|
||||
setConcurrency(concurrency)
|
||||
case "-p":
|
||||
fallthrough
|
||||
case "--password":
|
||||
tmpIndex := index + 1
|
||||
if index > len(os.Args) {
|
||||
break
|
||||
}
|
||||
setPassword(os.Args[tmpIndex])
|
||||
case "-u":
|
||||
fallthrough
|
||||
case "--user":
|
||||
tmpIndex := index + 1
|
||||
if index > len(os.Args) {
|
||||
break
|
||||
}
|
||||
setUser(os.Args[tmpIndex])
|
||||
default:
|
||||
arguments = append(arguments, arg)
|
||||
}
|
||||
}
|
||||
setSource(arguments[0])
|
||||
setTarget(arguments[1])
|
||||
if Concurrency == 0 {
|
||||
setConcurrency(runtime.NumCPU())
|
||||
}
|
||||
_, error := os.Stat(Source)
|
||||
if os.IsNotExist(error) {
|
||||
log.Fatal("given source does not exist", Source)
|
||||
}
|
||||
if !Verbose {
|
||||
setVerbose(false)
|
||||
}
|
||||
}
|
||||
|
||||
// unexported function(s)
|
||||
func removeArgument(index int) {
|
||||
removeArguments(index, 0, 0)
|
||||
}
|
||||
|
||||
func removeArguments(index int, before int, after int) {
|
||||
// derp := index - 1 - before
|
||||
copyArgs := os.Args[0 : index-before]
|
||||
copyArgs = append(copyArgs, os.Args[index+1+after:]...)
|
||||
os.Args = copyArgs
|
||||
}
|
56
settings/variables.go
Normal file
56
settings/variables.go
Normal file
|
@ -0,0 +1,56 @@
|
|||
package settings
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"velvettear/gosync/log"
|
||||
)
|
||||
|
||||
// exported variable(s)
|
||||
var Verbose bool
|
||||
var Source string
|
||||
var Target string
|
||||
var Concurrency int
|
||||
var Password string
|
||||
var User string
|
||||
|
||||
// exported function(s)
|
||||
func TargetIsRemote() bool {
|
||||
return strings.Contains(Target, ":")
|
||||
}
|
||||
|
||||
// unexported function(s)
|
||||
func setVerbose(verbose bool) {
|
||||
Verbose = verbose
|
||||
if Verbose {
|
||||
log.SetLogLevel(0)
|
||||
} else {
|
||||
log.SetLogLevel(1)
|
||||
}
|
||||
log.Debug("set verbose flag", strconv.FormatBool(Verbose))
|
||||
}
|
||||
|
||||
func setSource(source string) {
|
||||
Source = source
|
||||
log.Debug("set source", Source)
|
||||
}
|
||||
|
||||
func setTarget(target string) {
|
||||
Target = target
|
||||
log.Debug("set target", Target)
|
||||
}
|
||||
|
||||
func setConcurrency(concurrency int) {
|
||||
Concurrency = concurrency
|
||||
log.Debug("set concurrency", strconv.Itoa(Concurrency))
|
||||
}
|
||||
|
||||
func setPassword(password string) {
|
||||
Password = password
|
||||
log.Debug("set password", Password)
|
||||
}
|
||||
|
||||
func setUser(user string) {
|
||||
User = user
|
||||
log.Debug("set user", User)
|
||||
}
|
232
tools/rsync.go
Normal file
232
tools/rsync.go
Normal file
|
@ -0,0 +1,232 @@
|
|||
package tools
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
"velvettear/gosync/log"
|
||||
"velvettear/gosync/settings"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/vbauerster/mpb/v8"
|
||||
"github.com/vbauerster/mpb/v8/decor"
|
||||
)
|
||||
|
||||
var transferSize float64
|
||||
|
||||
// exported function(s)
|
||||
func Transfer() {
|
||||
transferSize = 0
|
||||
sourcefiles := getSourceFiles()
|
||||
sourcefilesCount := len(sourcefiles)
|
||||
var waitgroup sync.WaitGroup
|
||||
waitgroup.Add(sourcefilesCount)
|
||||
barcontainer := mpb.New(
|
||||
mpb.WithWaitGroup(&waitgroup),
|
||||
)
|
||||
timestamp := time.Now()
|
||||
counter := 0
|
||||
totalbar := createProgressBar(barcontainer, "Total", int64(0), int64(sourcefilesCount), sourcefilesCount+1, true)
|
||||
channel := make(chan struct{}, settings.Concurrency)
|
||||
for index, file := range sourcefiles {
|
||||
channel <- struct{}{}
|
||||
if index > 0 {
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
go func(index int, file string) {
|
||||
defer waitgroup.Done()
|
||||
stats, error := os.Stat(file)
|
||||
if error != nil {
|
||||
log.Warning("encountered an error getting file size", error.Error())
|
||||
return
|
||||
}
|
||||
bar := createProgressBar(barcontainer, filepath.Base(file), stats.Size(), int64(100), index, false)
|
||||
if transferFile(bar, file) {
|
||||
counter++
|
||||
}
|
||||
totalbar.Increment()
|
||||
<-channel
|
||||
}(index, file)
|
||||
}
|
||||
barcontainer.Wait()
|
||||
timeDifference := time.Since(timestamp)
|
||||
transferSpeedName := "bytes/sec"
|
||||
transferSpeed := transferSize / timeDifference.Seconds()
|
||||
if transferSpeed > 1048576 {
|
||||
transferSpeed = transferSpeed / 1048576
|
||||
transferSpeedName = "mb/s"
|
||||
}
|
||||
transferSizeName := "bytes"
|
||||
if transferSize > 1048576 {
|
||||
transferSize = transferSize / 1048576
|
||||
transferSizeName = "mb"
|
||||
}
|
||||
log.InfoTimed("transferred "+strconv.Itoa(counter)+" files, "+strconv.Itoa(int(transferSize))+" "+transferSizeName+" ("+strconv.FormatFloat(transferSpeed, 'f', 2, 64)+" "+transferSpeedName+")", timestamp.UnixMilli())
|
||||
}
|
||||
|
||||
// unexported function(s)
|
||||
func transferFile(bar *mpb.Bar, file string) bool {
|
||||
target := getTargetLocation(file)
|
||||
var arguments []string
|
||||
if len(settings.Password) > 0 {
|
||||
arguments = append(arguments, "-p", settings.Password)
|
||||
}
|
||||
arguments = append(arguments, "rsync", "-avz", "-mkpath", file)
|
||||
if len(settings.User) > 0 {
|
||||
target = settings.User + "@" + target
|
||||
}
|
||||
arguments = append(arguments, target, "--progress")
|
||||
cmd := exec.Command("sshpass", arguments...)
|
||||
stdout, stdoutError := cmd.StdoutPipe()
|
||||
stderr, stderrError := cmd.StderrPipe()
|
||||
cmd.Start()
|
||||
if stdoutError != nil {
|
||||
log.Fatal(stdoutError.Error())
|
||||
}
|
||||
if stderrError != nil {
|
||||
log.Fatal(stderrError.Error())
|
||||
}
|
||||
var resultBytes []byte
|
||||
var waitgroup sync.WaitGroup
|
||||
waitgroup.Add(1)
|
||||
go func() {
|
||||
defer waitgroup.Done()
|
||||
for {
|
||||
tmp := make([]byte, 1024)
|
||||
readBytes, error := stdout.Read(tmp)
|
||||
if error != nil {
|
||||
if error != io.EOF {
|
||||
log.Warning("encountered an error reading stdout", error.Error())
|
||||
}
|
||||
break
|
||||
}
|
||||
if readBytes == 0 {
|
||||
break
|
||||
}
|
||||
resultBytes = append(resultBytes, tmp...)
|
||||
line := string(tmp)
|
||||
lowerline := strings.ToLower(line)
|
||||
if strings.Contains(lowerline, "sent") && strings.Contains(lowerline, "received") && strings.Contains(lowerline, "bytes") {
|
||||
_, tmp, _ := strings.Cut(lowerline, "sent")
|
||||
tmp, _, _ = strings.Cut(tmp, "bytes")
|
||||
tmp = strings.ReplaceAll(strings.TrimSpace(tmp), ".", "")
|
||||
bytes, error := strconv.ParseFloat(tmp, 64)
|
||||
if error != nil {
|
||||
log.Fatal("encountered an error converting the transferred bytes to int", error.Error())
|
||||
}
|
||||
transferSize += bytes
|
||||
}
|
||||
if strings.Contains(lowerline, "total size is") && strings.Contains(lowerline, "speedup is") {
|
||||
bar.SetCurrent(100)
|
||||
break
|
||||
}
|
||||
cut := strings.Index(line, "%")
|
||||
if cut <= 0 {
|
||||
continue
|
||||
}
|
||||
line = line[:cut]
|
||||
var percent string
|
||||
for index := len(line) - 1; index > 0; index-- {
|
||||
char := string(line[index])
|
||||
if char == " " {
|
||||
break
|
||||
}
|
||||
percent = char + percent
|
||||
}
|
||||
value, error := strconv.Atoi(percent)
|
||||
if error != nil {
|
||||
continue
|
||||
}
|
||||
bar.SetCurrent(int64(value))
|
||||
}
|
||||
}()
|
||||
errorBytes, stderrError := io.ReadAll(stderr)
|
||||
if stderrError != nil {
|
||||
log.Fatal(stderrError.Error())
|
||||
}
|
||||
cmd.Wait()
|
||||
error := strings.Trim(string(errorBytes), "\n")
|
||||
if len(error) > 0 {
|
||||
log.Fatal(error)
|
||||
}
|
||||
waitgroup.Wait()
|
||||
stdout.Close()
|
||||
stderr.Close()
|
||||
return true
|
||||
}
|
||||
|
||||
func getTargetLocation(source string) string {
|
||||
if source == settings.Source {
|
||||
return filepath.Join(settings.Target, filepath.Base(source))
|
||||
}
|
||||
return filepath.Join(settings.Target, strings.Replace(source, filepath.Dir(settings.Source), "", 1))
|
||||
}
|
||||
|
||||
func createProgressBar(barcontainer *mpb.Progress, name string, size int64, max int64, priority int, total bool) *mpb.Bar {
|
||||
red, green, magenta, yellow := color.New(color.FgRed), color.New(color.FgGreen), color.New(color.FgMagenta), color.New(color.FgYellow)
|
||||
barstyle := mpb.BarStyle().Lbound("").Filler("").Tip("").Padding(" ").Rbound("")
|
||||
defaultBarPrepend := mpb.PrependDecorators(
|
||||
decor.Name("[info] > "),
|
||||
decor.OnCompleteMeta(
|
||||
decor.OnComplete(
|
||||
decor.Meta(decor.Name(name, decor.WCSyncSpaceR), colorize(red)), ""+name,
|
||||
),
|
||||
colorize(green),
|
||||
),
|
||||
)
|
||||
defaultBarAppend := mpb.AppendDecorators(
|
||||
decor.OnCompleteMeta(decor.Elapsed(decor.ET_STYLE_GO, decor.WCSyncSpaceR), colorize(yellow)),
|
||||
decor.OnComplete(
|
||||
decor.Name("|", decor.WCSyncSpaceR), "",
|
||||
),
|
||||
decor.OnComplete(
|
||||
decor.Percentage(decor.WCSyncSpaceR), "",
|
||||
),
|
||||
)
|
||||
totalBarPrepend := mpb.PrependDecorators(
|
||||
decor.Name("[info] > "),
|
||||
decor.OnCompleteMeta(
|
||||
decor.OnComplete(
|
||||
decor.Meta(decor.Name(name, decor.WC{W: len(name) + 1, C: decor.DidentRight}), colorize(yellow)), ""+name,
|
||||
),
|
||||
colorize(magenta),
|
||||
),
|
||||
decor.CountersNoUnit("%d / %d"),
|
||||
)
|
||||
if total {
|
||||
return barcontainer.New(
|
||||
max,
|
||||
barstyle,
|
||||
mpb.BarPriority(priority),
|
||||
mpb.BarFillerClearOnComplete(),
|
||||
totalBarPrepend,
|
||||
defaultBarAppend,
|
||||
)
|
||||
} else {
|
||||
return barcontainer.New(
|
||||
max,
|
||||
barstyle,
|
||||
mpb.BarPriority(priority),
|
||||
mpb.BarFillerClearOnComplete(),
|
||||
defaultBarPrepend,
|
||||
defaultBarAppend,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func colorize(c *color.Color) func(string) string {
|
||||
return func(s string) string {
|
||||
return c.Sprint(s)
|
||||
}
|
||||
}
|
||||
|
||||
func decolorize() func(string) string {
|
||||
return func(s string) string {
|
||||
return ""
|
||||
}
|
||||
}
|
41
tools/scanner.go
Normal file
41
tools/scanner.go
Normal file
|
@ -0,0 +1,41 @@
|
|||
package tools
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"time"
|
||||
"velvettear/gosync/log"
|
||||
"velvettear/gosync/settings"
|
||||
)
|
||||
|
||||
var sourceFiles []string
|
||||
|
||||
// unexported function(s)
|
||||
func getSourceFiles() []string {
|
||||
timestamp := time.Now()
|
||||
stats, error := os.Stat(settings.Source)
|
||||
if error != nil {
|
||||
log.Error("encountered an error getting the stats for the source", error.Error())
|
||||
}
|
||||
if stats.IsDir() {
|
||||
log.Info("scanning source...", settings.Source)
|
||||
filepath.WalkDir(settings.Source, fillSourceFiles)
|
||||
log.InfoTimed("found "+strconv.Itoa(len(sourceFiles))+" source files", timestamp.UnixMilli())
|
||||
} else {
|
||||
sourceFiles = append(sourceFiles, settings.Source)
|
||||
}
|
||||
return sourceFiles
|
||||
}
|
||||
|
||||
func fillSourceFiles(path string, dir fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if dir.IsDir() {
|
||||
return nil
|
||||
}
|
||||
sourceFiles = append(sourceFiles, path)
|
||||
return nil
|
||||
}
|
55
tools/ssh.go
Normal file
55
tools/ssh.go
Normal file
|
@ -0,0 +1,55 @@
|
|||
package tools
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
|
||||
"os/exec"
|
||||
"strings"
|
||||
"velvettear/gosync/log"
|
||||
"velvettear/gosync/settings"
|
||||
)
|
||||
|
||||
// exported function(s)
|
||||
func TestConnection() error {
|
||||
if !settings.TargetIsRemote() {
|
||||
return nil
|
||||
}
|
||||
if len(settings.Password) == 0 {
|
||||
log.Warning("target is a remote host and no password is set, make sure passwordless login is configured")
|
||||
}
|
||||
var arguments []string
|
||||
if len(settings.Password) > 0 {
|
||||
arguments = append(arguments, "-p", settings.Password)
|
||||
}
|
||||
arguments = append(arguments, "ssh")
|
||||
target, _, _ := strings.Cut(settings.Target, ":")
|
||||
if len(settings.User) > 0 {
|
||||
target = settings.User + "@" + target
|
||||
}
|
||||
arguments = append(arguments, target, "'exit'")
|
||||
cmd := exec.Command("sshpass", arguments...)
|
||||
stdout, stdoutError := cmd.StdoutPipe()
|
||||
stderr, stderrError := cmd.StderrPipe()
|
||||
cmd.Start()
|
||||
if stdoutError != nil {
|
||||
return stdoutError
|
||||
}
|
||||
if stderrError != nil {
|
||||
return stderrError
|
||||
}
|
||||
_, stdoutError = io.ReadAll(stdout)
|
||||
if stdoutError != nil {
|
||||
return stdoutError
|
||||
}
|
||||
errorBytes, stderrError := io.ReadAll(stderr)
|
||||
if stderrError != nil {
|
||||
return stderrError
|
||||
}
|
||||
cmd.Wait()
|
||||
error := strings.TrimSpace(string(errorBytes))
|
||||
if len(error) > 0 {
|
||||
return errors.New(error)
|
||||
}
|
||||
return nil
|
||||
}
|
Loading…
Reference in a new issue