mirror of
https://github.com/gilbertchen/duplicacy
synced 2025-12-11 05:43:23 +00:00
Move source files into src for a better looking front page
This commit is contained in:
391
src/duplicacy_utils.go
Normal file
391
src/duplicacy_utils.go
Normal file
@@ -0,0 +1,391 @@
|
||||
// Copyright (c) Acrosync LLC. All rights reserved.
|
||||
// Licensed under the Fair Source License 0.9 (https://fair.io/)
|
||||
// User Limitation: 5 users
|
||||
|
||||
package duplicacy
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"bufio"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"time"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"strconv"
|
||||
"runtime"
|
||||
"crypto/sha256"
|
||||
|
||||
"golang.org/x/crypto/pbkdf2"
|
||||
"github.com/gilbertchen/gopass"
|
||||
)
|
||||
|
||||
var RunInBackground bool = false
|
||||
|
||||
type RateLimitedReader struct {
|
||||
Content []byte
|
||||
Rate float64
|
||||
Next int
|
||||
StartTime time.Time
|
||||
}
|
||||
|
||||
func CreateRateLimitedReader(content []byte, rate int) (*RateLimitedReader) {
|
||||
return &RateLimitedReader {
|
||||
Content: content,
|
||||
Rate: float64(rate * 1024),
|
||||
Next: 0,
|
||||
}
|
||||
}
|
||||
|
||||
func (reader *RateLimitedReader) Length() (int64) {
|
||||
return int64(len(reader.Content))
|
||||
}
|
||||
|
||||
func (reader *RateLimitedReader) Reset() {
|
||||
reader.Next = 0
|
||||
}
|
||||
|
||||
|
||||
func (reader *RateLimitedReader) Read(p []byte) (n int, err error) {
|
||||
|
||||
if reader.Next >= len(reader.Content) {
|
||||
return 0, io.EOF
|
||||
}
|
||||
|
||||
if reader.Rate <= 0 {
|
||||
n := copy(p, reader.Content[reader.Next:])
|
||||
reader.Next += n
|
||||
if reader.Next >= len(reader.Content) {
|
||||
return n, io.EOF
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
if reader.StartTime.IsZero() {
|
||||
reader.StartTime = time.Now()
|
||||
}
|
||||
|
||||
elapsed := time.Since(reader.StartTime).Seconds()
|
||||
delay := float64(reader.Next) / reader.Rate - elapsed
|
||||
end := reader.Next + int(reader.Rate / 5)
|
||||
if delay > 0 {
|
||||
time.Sleep(time.Duration(delay * float64(time.Second)))
|
||||
} else {
|
||||
end += - int(delay * reader.Rate)
|
||||
}
|
||||
|
||||
if end > len(reader.Content) {
|
||||
end = len(reader.Content)
|
||||
}
|
||||
|
||||
n = copy(p, reader.Content[reader.Next : end])
|
||||
reader.Next += n
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func RateLimitedCopy(writer io.Writer, reader io.Reader, rate int) (written int64, err error) {
|
||||
if rate <= 0 {
|
||||
return io.Copy(writer, reader)
|
||||
}
|
||||
for range time.Tick(time.Second / 5) {
|
||||
n, err := io.CopyN(writer, reader, int64(rate * 1024 / 5))
|
||||
written += n
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
return written, nil
|
||||
} else {
|
||||
return written, err
|
||||
}
|
||||
}
|
||||
}
|
||||
return written, nil
|
||||
}
|
||||
|
||||
// GenerateKeyFromPassword generates a key from the password.
|
||||
func GenerateKeyFromPassword(password string) []byte {
|
||||
return pbkdf2.Key([]byte(password), DEFAULT_KEY, 16384, 32, sha256.New)
|
||||
}
|
||||
|
||||
// GetPassword attempts to get the password from KeyChain/KeyRing, environment variables, or keyboard input.
|
||||
func GetPassword(preference Preference, passwordType string, prompt string,
|
||||
showPassword bool, resetPassword bool) (string) {
|
||||
|
||||
passwordID := passwordType
|
||||
if preference.Name != "default" {
|
||||
passwordID = preference.Name + "_" + passwordID
|
||||
}
|
||||
|
||||
{
|
||||
name := strings.ToUpper("duplicacy_" + passwordID)
|
||||
LOG_DEBUG("PASSWORD_ENV_VAR", "Reading the environment variable %s", name)
|
||||
if password, found := os.LookupEnv(name); found && password != "" {
|
||||
return password
|
||||
}
|
||||
}
|
||||
|
||||
if len(preference.Keys) > 0 && len(preference.Keys[passwordID]) > 0 {
|
||||
LOG_DEBUG("PASSWORD_KEYCHAIN", "Reading %s from preferences", passwordID)
|
||||
return preference.Keys[passwordID]
|
||||
}
|
||||
|
||||
if resetPassword && !RunInBackground {
|
||||
keyringSet(passwordID, "")
|
||||
} else {
|
||||
password := keyringGet(passwordID)
|
||||
if password != "" {
|
||||
return password
|
||||
}
|
||||
|
||||
if RunInBackground {
|
||||
LOG_INFO("PASSWORD_MISSING", "%s is not found in Keychain/Keyring", passwordID)
|
||||
return ""
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
password := ""
|
||||
fmt.Printf("%s", prompt)
|
||||
if showPassword {
|
||||
scanner := bufio.NewScanner(os.Stdin)
|
||||
scanner.Scan()
|
||||
password = scanner.Text()
|
||||
} else {
|
||||
passwordInBytes, err := gopass.GetPasswdMasked()
|
||||
if err != nil {
|
||||
LOG_ERROR("PASSWORD_READ", "Failed to read the password: %v", err)
|
||||
return ""
|
||||
}
|
||||
password = string(passwordInBytes)
|
||||
}
|
||||
|
||||
return password
|
||||
}
|
||||
|
||||
// SavePassword saves the specified password in the keyring/keychain.
|
||||
func SavePassword(preference Preference, passwordType string, password string) {
|
||||
if password == "" || RunInBackground {
|
||||
return
|
||||
}
|
||||
|
||||
if preference.DoNotSavePassword {
|
||||
return
|
||||
}
|
||||
passwordID := passwordType
|
||||
if preference.Name != "default" {
|
||||
passwordID = preference.Name + "_" + passwordID
|
||||
}
|
||||
keyringSet(passwordID, password)
|
||||
}
|
||||
|
||||
// RemoveEmptyDirectories remove all empty subdirectoreies under top.
|
||||
func RemoveEmptyDirectories(top string) {
|
||||
|
||||
stack := make([]string, 0, 256)
|
||||
|
||||
stack = append(stack, top)
|
||||
|
||||
for len(stack) > 0 {
|
||||
|
||||
dir := stack[len(stack) - 1]
|
||||
stack = stack[:len(stack) - 1]
|
||||
|
||||
files, err := ioutil.ReadDir(dir)
|
||||
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
if file.IsDir() && file.Name()[0] != '.' {
|
||||
stack = append(stack, path.Join(dir, file.Name()))
|
||||
}
|
||||
}
|
||||
|
||||
if len(files) == 0 {
|
||||
if os.Remove(dir) != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
dir = path.Dir(dir)
|
||||
for (len(dir) > len(top)) {
|
||||
files, err := ioutil.ReadDir(dir)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
if len(files) == 0 {
|
||||
if os.Remove(dir) != nil {
|
||||
break;
|
||||
}
|
||||
}
|
||||
dir = path.Dir(dir)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// The following code was modified from the online article 'Matching Wildcards: An Algorithm', by Kirk J. Krauss,
|
||||
// Dr. Dobb's, August 26, 2008. However, the version in the article doesn't handle cases like matching 'abcccd'
|
||||
// against '*ccd', and the version here fixed that issue.
|
||||
//
|
||||
func matchPattern(text string, pattern string) bool {
|
||||
|
||||
textLength := len(text)
|
||||
patternLength := len(pattern)
|
||||
afterLastWildcard := 0
|
||||
afterLastMatched := 0
|
||||
|
||||
t := 0
|
||||
p := 0
|
||||
|
||||
for {
|
||||
if t >= textLength {
|
||||
if p >= patternLength {
|
||||
return true // "x" matches "x"
|
||||
} else if pattern[p] == '*' {
|
||||
p++
|
||||
continue // "x*" matches "x" or "xy"
|
||||
}
|
||||
return false // "x" doesn't match "xy"
|
||||
}
|
||||
|
||||
w := byte(0)
|
||||
if p < patternLength {
|
||||
w = pattern[p]
|
||||
}
|
||||
|
||||
if text[t] != w {
|
||||
if w == '?' {
|
||||
t++
|
||||
p++
|
||||
continue
|
||||
} else if w == '*' {
|
||||
p++
|
||||
afterLastWildcard = p
|
||||
if p >= patternLength {
|
||||
return true
|
||||
}
|
||||
} else if afterLastWildcard > 0 {
|
||||
p = afterLastWildcard
|
||||
t = afterLastMatched
|
||||
t++
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
|
||||
for t < textLength && text[t] != pattern[p] && pattern[p] != '?' {
|
||||
t++
|
||||
}
|
||||
|
||||
if t >= textLength {
|
||||
return false
|
||||
}
|
||||
afterLastMatched = t
|
||||
}
|
||||
t++
|
||||
p++
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MatchPath returns 'true' if the file 'filePath' is excluded by the specified 'patterns'. Each pattern starts with
|
||||
// either '+' or '-', whereas '-' indicates exclusion and '+' indicates inclusion. Wildcards like '*' and '?' may
|
||||
// appear in the patterns. In case no matching pattern is found, the file will be excluded if all patterns are
|
||||
// include patterns, and included otherwise.
|
||||
func MatchPath(filePath string, patterns [] string) (included bool) {
|
||||
|
||||
allIncludes := true
|
||||
for _, pattern := range patterns {
|
||||
|
||||
if pattern[0] == '+' {
|
||||
if matchPattern(filePath, pattern[1:]) {
|
||||
return true
|
||||
}
|
||||
} else if pattern[0] == '-' {
|
||||
allIncludes = false
|
||||
if matchPattern(filePath, pattern[1:]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return !allIncludes
|
||||
}
|
||||
|
||||
func joinPath(components ...string) string {
|
||||
|
||||
combinedPath := path.Join(components...)
|
||||
if len(combinedPath) > 257 && runtime.GOOS == "windows" {
|
||||
combinedPath = `\\?\` + filepath.Join(components...)
|
||||
}
|
||||
return combinedPath
|
||||
}
|
||||
|
||||
func PrettyNumber(number int64) (string) {
|
||||
|
||||
G := int64(1024 * 1024 * 1024)
|
||||
M := int64(1024 * 1024)
|
||||
K := int64(1024)
|
||||
|
||||
if number > 1000 * G {
|
||||
return fmt.Sprintf("%dG", number / G)
|
||||
} else if number > G {
|
||||
return fmt.Sprintf("%d,%03dM", number / (1000 * M), (number / M) % 1000)
|
||||
} else if number > M {
|
||||
return fmt.Sprintf("%d,%03dK", number / (1000 * K), (number / K) % 1000)
|
||||
} else if number > K {
|
||||
return fmt.Sprintf("%dK", number / K)
|
||||
} else {
|
||||
return fmt.Sprintf("%d", number)
|
||||
}
|
||||
}
|
||||
|
||||
func PrettySize(size int64) (string) {
|
||||
if size > 1024 * 1024 {
|
||||
return fmt.Sprintf("%.2fM", float64(size) / (1024.0 * 1024.0))
|
||||
} else if size > 1024 {
|
||||
return fmt.Sprintf("%.0fK", float64(size) / 1024.0)
|
||||
} else {
|
||||
return fmt.Sprintf("%d", size)
|
||||
}
|
||||
}
|
||||
|
||||
func PrettyTime(seconds int64) (string) {
|
||||
|
||||
day := int64(3600 * 24)
|
||||
|
||||
if seconds > day * 2 {
|
||||
return fmt.Sprintf("%d days %02d:%02d:%02d",
|
||||
seconds / day, (seconds % day) / 3600, (seconds % 3600) / 60, seconds % 60)
|
||||
} else if seconds > day {
|
||||
return fmt.Sprintf("1 day %02d:%02d:%02d", (seconds % day) / 3600, (seconds % 3600) / 60, seconds % 60)
|
||||
} else if seconds > 0 {
|
||||
return fmt.Sprintf("%02d:%02d:%02d", seconds / 3600, (seconds % 3600) / 60, seconds % 60)
|
||||
} else {
|
||||
return "n/a"
|
||||
}
|
||||
}
|
||||
|
||||
func AtoSize(sizeString string) (int) {
|
||||
sizeString = strings.ToLower(sizeString)
|
||||
|
||||
sizeRegex := regexp.MustCompile(`^([0-9]+)([mk])?$`)
|
||||
matched := sizeRegex.FindStringSubmatch(sizeString)
|
||||
if matched == nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
size, _ := strconv.Atoi(matched[1])
|
||||
|
||||
if matched[2] == "m" {
|
||||
size *= 1024 * 1024
|
||||
} else if matched[2] == "k" {
|
||||
size *= 1024
|
||||
}
|
||||
|
||||
return size
|
||||
}
|
||||
Reference in New Issue
Block a user