mirror of
https://github.com/j3ssie/osmedeus.git
synced 2026-09-28 04:24:59 +02:00
997 lines
20 KiB
Go
997 lines
20 KiB
Go
package utils
|
|
|
|
import (
|
|
"archive/zip"
|
|
"bufio"
|
|
"bytes"
|
|
"context"
|
|
"crypto/sha1"
|
|
"encoding/base64"
|
|
"fmt"
|
|
"io"
|
|
"math/rand"
|
|
"net/url"
|
|
"os"
|
|
"os/exec"
|
|
"path"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
|
|
//"syscall"
|
|
"text/template"
|
|
"time"
|
|
|
|
"github.com/mitchellh/go-homedir"
|
|
)
|
|
|
|
// CalcTimeout calculate timeout
|
|
func CalcTimeout(raw string) int {
|
|
raw = strings.ToLower(strings.TrimSpace(raw))
|
|
seconds := raw
|
|
multiply := 1
|
|
|
|
matched, _ := regexp.MatchString(`.*[a-z]`, raw)
|
|
if matched {
|
|
unitTime := fmt.Sprintf("%c", raw[len(raw)-1])
|
|
seconds = raw[:len(raw)-1]
|
|
switch unitTime {
|
|
case "s":
|
|
multiply = 1
|
|
break
|
|
case "m":
|
|
multiply = 60
|
|
break
|
|
case "h":
|
|
multiply = 3600
|
|
break
|
|
}
|
|
}
|
|
|
|
timeout, err := strconv.Atoi(seconds)
|
|
if err != nil {
|
|
return 0
|
|
}
|
|
return timeout * multiply
|
|
}
|
|
|
|
// GetDomain get domain from the URL
|
|
func GetDomain(raw string) (string, error) {
|
|
u, err := url.Parse(raw)
|
|
if err == nil {
|
|
return u.Hostname(), nil
|
|
}
|
|
return raw, err
|
|
}
|
|
|
|
// EmptyDir check if directory is empty or not
|
|
func EmptyDir(dir string) bool {
|
|
if !FolderExists(dir) {
|
|
return true
|
|
}
|
|
f, err := os.Open(NormalizePath(dir))
|
|
if err != nil {
|
|
return false
|
|
}
|
|
defer f.Close()
|
|
|
|
_, err = f.Readdirnames(1)
|
|
if err == io.EOF {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// EmptyFile check if file is empty or not
|
|
func EmptyFile(filename string, num int) bool {
|
|
filename = NormalizePath(filename)
|
|
if !FileExists(filename) {
|
|
return true
|
|
}
|
|
data := ReadingLines(filename)
|
|
if len(data) > num {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
// StrToInt string to int
|
|
func StrToInt(data string) int {
|
|
i, err := strconv.Atoi(data)
|
|
if err != nil {
|
|
return 0
|
|
}
|
|
return i
|
|
}
|
|
|
|
// GetOSEnv get environment variable
|
|
func GetOSEnv(name string, defaultValue string) string {
|
|
variable, ok := os.LookupEnv(name)
|
|
if !ok {
|
|
if defaultValue != "" {
|
|
return defaultValue
|
|
}
|
|
return name
|
|
}
|
|
return variable
|
|
}
|
|
|
|
// MakeDir just make a folder
|
|
func MakeDir(folder string) {
|
|
folder = NormalizePath(folder)
|
|
os.MkdirAll(folder, 0750)
|
|
}
|
|
|
|
// GetCurrentDay get current day
|
|
func GetCurrentDay() string {
|
|
currentTime := time.Now()
|
|
return fmt.Sprintf("%v", currentTime.Format("2006-01-02_3:4:5"))
|
|
}
|
|
|
|
// NormalizePath the path
|
|
func NormalizePath(path string) string {
|
|
if strings.HasPrefix(path, "~") {
|
|
path, _ = homedir.Expand(path)
|
|
}
|
|
return path
|
|
}
|
|
|
|
// GetFileContent Reading file and return content of it
|
|
func GetFileContent(filename string) string {
|
|
var result string
|
|
if strings.Contains(filename, "~") {
|
|
filename, _ = homedir.Expand(filename)
|
|
}
|
|
file, err := os.Open(filename)
|
|
if err != nil {
|
|
return result
|
|
}
|
|
defer file.Close()
|
|
b, err := io.ReadAll(file)
|
|
if err != nil {
|
|
return result
|
|
}
|
|
return string(b)
|
|
}
|
|
|
|
// ReadingLines Reading file and return content as []string
|
|
func ReadingLines(filename string) []string {
|
|
var result []string
|
|
if strings.HasPrefix(filename, "~") {
|
|
filename, _ = homedir.Expand(filename)
|
|
}
|
|
file, err := os.Open(filename)
|
|
if err != nil {
|
|
return result
|
|
}
|
|
defer file.Close()
|
|
|
|
// scanner := bufio.NewScanner(file)
|
|
// for scanner.Scan() {
|
|
// val := strings.TrimSpace(scanner.Text())
|
|
// if val == "" {
|
|
// continue
|
|
// }
|
|
// result = append(result, val)
|
|
// }
|
|
|
|
// increase the buffer size
|
|
scanner := bufio.NewScanner(file)
|
|
scanner.Buffer(make([]byte, 0, 64*1024), 1024*1024)
|
|
for scanner.Scan() {
|
|
val := strings.TrimSpace(scanner.Text())
|
|
if val == "" {
|
|
continue
|
|
}
|
|
result = append(result, val)
|
|
}
|
|
|
|
if err := scanner.Err(); err != nil {
|
|
return result
|
|
}
|
|
return result
|
|
}
|
|
|
|
// Cat Reading file and return content as []string
|
|
func Cat(filename string) {
|
|
filename = NormalizePath(filename)
|
|
if !FileExists(filename) {
|
|
return
|
|
}
|
|
file, err := os.Open(filename)
|
|
if err != nil {
|
|
return
|
|
}
|
|
defer file.Close()
|
|
|
|
scanner := bufio.NewScanner(file)
|
|
for scanner.Scan() {
|
|
line := strings.TrimSpace(scanner.Text())
|
|
if line == "" {
|
|
continue
|
|
}
|
|
fmt.Println(line)
|
|
}
|
|
return
|
|
}
|
|
|
|
// ReadingFileUnique Reading file and return content as []string
|
|
func ReadingFileUnique(filename string) []string {
|
|
var result []string
|
|
if strings.Contains(filename, "~") {
|
|
filename, _ = homedir.Expand(filename)
|
|
}
|
|
file, err := os.Open(filename)
|
|
if err != nil {
|
|
return result
|
|
}
|
|
defer file.Close()
|
|
|
|
seen := make(map[string]bool)
|
|
scanner := bufio.NewScanner(file)
|
|
for scanner.Scan() {
|
|
val := strings.TrimSpace(scanner.Text())
|
|
// unique stuff
|
|
if val == "" {
|
|
continue
|
|
}
|
|
if seen[val] {
|
|
continue
|
|
}
|
|
|
|
seen[val] = true
|
|
result = append(result, val)
|
|
}
|
|
|
|
if err := scanner.Err(); err != nil {
|
|
return result
|
|
}
|
|
return result
|
|
}
|
|
|
|
// WriteToFile write string to a file
|
|
func WriteToFile(filename string, data string) (string, error) {
|
|
file, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
defer file.Close()
|
|
|
|
_, err = io.WriteString(file, data+"\n")
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return filename, file.Sync()
|
|
}
|
|
|
|
// AppendToContent append string to a file
|
|
func AppendToContent(filename string, data string) (string, error) {
|
|
// If the file doesn't exist, create it, or append to the file
|
|
f, err := os.OpenFile(filename, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if _, err := f.Write([]byte(data + "\n")); err != nil {
|
|
return "", err
|
|
}
|
|
if err := f.Close(); err != nil {
|
|
return "", err
|
|
}
|
|
return filename, nil
|
|
}
|
|
|
|
// FileExists check if file is exist or not
|
|
func FileExists(filename string) bool {
|
|
filename = NormalizePath(filename)
|
|
_, err := os.Stat(filename)
|
|
if os.IsNotExist(err) {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
// FolderExists check if file is exist or not
|
|
func FolderExists(foldername string) bool {
|
|
foldername = NormalizePath(foldername)
|
|
if _, err := os.Stat(foldername); os.IsNotExist(err) {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
// FileLength count len of file
|
|
func FileLength(filename string) int {
|
|
filename = NormalizePath(filename)
|
|
if !FileExists(filename) {
|
|
return 0
|
|
}
|
|
return CountLines(filename)
|
|
}
|
|
|
|
// DirLength count len of file
|
|
func DirLength(dir string) int {
|
|
dir = NormalizePath(dir)
|
|
files, err := os.ReadDir(dir)
|
|
if err != nil {
|
|
dir = dir + "/"
|
|
files, err = os.ReadDir(dir)
|
|
if err == nil {
|
|
return len(files)
|
|
}
|
|
return 0
|
|
}
|
|
return len(files)
|
|
}
|
|
|
|
// Copy append content to a file
|
|
func Copy(src string, dest string) {
|
|
src = NormalizePath(src)
|
|
dest = NormalizePath(dest)
|
|
if !FileExists(src) || FileLength(src) <= 0 {
|
|
return
|
|
}
|
|
input, _ := os.ReadFile(src)
|
|
os.WriteFile(dest, input, 0644)
|
|
}
|
|
|
|
// GetTS get current timestamp and return a string
|
|
func GetTS() string {
|
|
return strconv.FormatInt(time.Now().Unix(), 10)
|
|
}
|
|
|
|
// GenHash gen SHA1 hash from string
|
|
func GenHash(text string) string {
|
|
h := sha1.New()
|
|
h.Write([]byte(text))
|
|
hashed := h.Sum(nil)
|
|
return fmt.Sprintf("%x", hashed)
|
|
}
|
|
|
|
// GetFileSize get file size of a file in GB
|
|
func GetFileSize(src string) float64 {
|
|
var sizeGB float64
|
|
fi, err := os.Stat(NormalizePath(src))
|
|
if err != nil {
|
|
return sizeGB
|
|
}
|
|
// get the size
|
|
size := fi.Size()
|
|
sizeGB = float64(size) / (1024 * 1024 * 1024)
|
|
return sizeGB
|
|
}
|
|
|
|
// RandomString return a random string with length
|
|
func RandomString(n int) string {
|
|
var seededRand = rand.New(rand.NewSource(time.Now().UnixNano()))
|
|
var letter = []rune("abcdefghijklmnopqrstuvwxyz")
|
|
b := make([]rune, n)
|
|
for i := range b {
|
|
b[i] = letter[seededRand.Intn(len(letter))]
|
|
}
|
|
return string(b)
|
|
}
|
|
|
|
// runCmdWithOutput just run os command
|
|
func runCmdWithOutput(cmd string) string {
|
|
DebugF("Execute: %s", cmd)
|
|
command := []string{
|
|
"bash",
|
|
"-c",
|
|
cmd,
|
|
}
|
|
realCmd := exec.Command(command[0], command[1:]...)
|
|
// output command output to std too
|
|
output, _ := realCmd.CombinedOutput()
|
|
return string(output)
|
|
}
|
|
|
|
// RunCmdWithOutput run command with timeout
|
|
func RunCmdWithOutput(command string, timeoutRaw ...string) string {
|
|
if len(timeoutRaw) == 0 {
|
|
return runCmdWithOutput(command)
|
|
}
|
|
|
|
timeout := CalcTimeout(timeoutRaw[0])
|
|
DebugF("Run command with %v seconds timeout", timeout)
|
|
var out string
|
|
|
|
c := context.Background()
|
|
deadline := time.Now().Add(time.Duration(timeout) * time.Second)
|
|
c, cancel := context.WithDeadline(c, deadline)
|
|
defer cancel()
|
|
go func() {
|
|
out = RunCmdWithOutput(command)
|
|
cancel()
|
|
}()
|
|
|
|
select {
|
|
case <-c.Done():
|
|
return out
|
|
case <-time.After(time.Duration(timeout) * time.Second):
|
|
return out + "\n[err] command got timeout"
|
|
}
|
|
}
|
|
|
|
func runCommandWithError(cmd string) (string, error) {
|
|
DebugF("Execute: %s", cmd)
|
|
command := []string{
|
|
"bash",
|
|
"-c",
|
|
cmd,
|
|
}
|
|
var output string
|
|
realCmd := exec.Command(command[0], command[1:]...)
|
|
|
|
// output command output to std too
|
|
cmdReader, _ := realCmd.StdoutPipe()
|
|
errReader, _ := realCmd.StderrPipe()
|
|
scanner := bufio.NewScanner(cmdReader)
|
|
errScanner := bufio.NewScanner(errReader)
|
|
go func() {
|
|
for scanner.Scan() {
|
|
out := scanner.Text()
|
|
DebugF(out)
|
|
output += out + "\n"
|
|
}
|
|
}()
|
|
go func() {
|
|
for errScanner.Scan() {
|
|
DebugF(errScanner.Text())
|
|
}
|
|
}()
|
|
if err := realCmd.Start(); err != nil {
|
|
return output, err
|
|
}
|
|
if err := realCmd.Wait(); err != nil {
|
|
return output, err
|
|
}
|
|
return output, nil
|
|
}
|
|
|
|
// RunCommandWithErr Run a command
|
|
func RunCommandWithErr(command string, timeoutRaw ...string) (string, error) {
|
|
if len(timeoutRaw) == 0 {
|
|
return runCommandWithError(command)
|
|
}
|
|
var output string
|
|
var err error
|
|
|
|
timeout := CalcTimeout(timeoutRaw[0])
|
|
DebugF("Run command with %v seconds timeout", timeout)
|
|
var out string
|
|
|
|
c := context.Background()
|
|
deadline := time.Now().Add(time.Duration(timeout) * time.Second)
|
|
c, cancel := context.WithDeadline(c, deadline)
|
|
defer cancel()
|
|
go func() {
|
|
out, err = runCommandWithError(command)
|
|
cancel()
|
|
}()
|
|
|
|
select {
|
|
case <-c.Done():
|
|
return output, err
|
|
case <-time.After(time.Duration(timeout) * time.Second):
|
|
return out, fmt.Errorf("command got timeout")
|
|
}
|
|
}
|
|
|
|
func RunCommandSteamOutput(cmd string) (string, error) {
|
|
DebugF("Execute: %s", cmd)
|
|
command := []string{
|
|
"bash",
|
|
"-c",
|
|
cmd,
|
|
}
|
|
var output string
|
|
realCmd := exec.Command(command[0], command[1:]...)
|
|
|
|
// output command output to std too
|
|
cmdReader, _ := realCmd.StdoutPipe()
|
|
errReader, _ := realCmd.StderrPipe()
|
|
scanner := bufio.NewScanner(cmdReader)
|
|
errScanner := bufio.NewScanner(errReader)
|
|
go func() {
|
|
for scanner.Scan() {
|
|
out := scanner.Text()
|
|
fmt.Println(out)
|
|
output += out
|
|
}
|
|
}()
|
|
go func() {
|
|
for errScanner.Scan() {
|
|
DebugF(errScanner.Text())
|
|
}
|
|
}()
|
|
if err := realCmd.Start(); err != nil {
|
|
return output, err
|
|
}
|
|
if err := realCmd.Wait(); err != nil {
|
|
return output, err
|
|
}
|
|
return output, nil
|
|
}
|
|
|
|
func RunOSCommand(cmd string) (string, error) {
|
|
DebugF("Execute: %s", cmd)
|
|
command := []string{
|
|
"bash",
|
|
"-c",
|
|
cmd,
|
|
}
|
|
var output string
|
|
realCmd := exec.Command(command[0], command[1:]...)
|
|
|
|
// output command output to std too
|
|
cmdReader, _ := realCmd.StdoutPipe()
|
|
errReader, _ := realCmd.StderrPipe()
|
|
scanner := bufio.NewScanner(cmdReader)
|
|
errScanner := bufio.NewScanner(errReader)
|
|
go func() {
|
|
for scanner.Scan() {
|
|
out := scanner.Text()
|
|
DebugF(out)
|
|
output += out
|
|
}
|
|
}()
|
|
go func() {
|
|
for errScanner.Scan() {
|
|
DebugF(errScanner.Text())
|
|
}
|
|
}()
|
|
if err := realCmd.Start(); err != nil {
|
|
return output, err
|
|
}
|
|
if err := realCmd.Wait(); err != nil {
|
|
return output, err
|
|
}
|
|
return output, nil
|
|
}
|
|
|
|
func RunOSCommandStream(cmd, std string) error {
|
|
DebugF("Execute: %s", cmd)
|
|
command := []string{
|
|
"bash",
|
|
"-c",
|
|
cmd,
|
|
}
|
|
file, _ := os.OpenFile(std, os.O_WRONLY, os.ModeAppend)
|
|
realCmd := exec.Command(command[0], command[1:]...)
|
|
// realCmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
|
|
|
|
// output command output to std too
|
|
realCmd.Stdout = file
|
|
realCmd.Stderr = file
|
|
if err := realCmd.Start(); err != nil {
|
|
return err
|
|
}
|
|
if err := realCmd.Wait(); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// RunCommandWithoutOutput Run a command
|
|
func RunCommandWithoutOutput(cmd string) error {
|
|
command := []string{
|
|
"bash",
|
|
"-c",
|
|
cmd,
|
|
}
|
|
DebugF("[Exec] %v", command)
|
|
realCmd := exec.Command(command[0], command[1:]...)
|
|
cmdReader, _ := realCmd.StdoutPipe()
|
|
errReader, _ := realCmd.StderrPipe()
|
|
scanner := bufio.NewScanner(cmdReader)
|
|
errScanner := bufio.NewScanner(errReader)
|
|
go func() {
|
|
for scanner.Scan() {
|
|
InforF(scanner.Text())
|
|
}
|
|
}()
|
|
go func() {
|
|
for errScanner.Scan() {
|
|
DebugF(errScanner.Text())
|
|
}
|
|
}()
|
|
if err := realCmd.Start(); err != nil {
|
|
return err
|
|
}
|
|
if err := realCmd.Wait(); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// StripPath just Base64 Encode
|
|
func StripPath(raw string) string {
|
|
raw = NormalizePath(raw)
|
|
raw = strings.Replace(raw, "/", "_", -1)
|
|
raw = strings.Replace(raw, "..", "_", -1)
|
|
return raw
|
|
}
|
|
|
|
// ZippedFolder zip a folder
|
|
func ZippedFolder(src string, dest string) error {
|
|
baseDest := path.Base(dest)
|
|
if FileExists(baseDest) {
|
|
os.RemoveAll(baseDest)
|
|
}
|
|
file, err := os.Create(dest + ".zip")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer file.Close()
|
|
|
|
w := zip.NewWriter(file)
|
|
defer w.Close()
|
|
|
|
walker := func(asbPath string, info os.FileInfo, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if info.IsDir() {
|
|
return nil
|
|
}
|
|
file, err := os.Open(asbPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer file.Close()
|
|
relPath := strings.Replace(asbPath, src, baseDest, -1)
|
|
f, err := w.Create(relPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
_, err = io.Copy(f, file)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
err = filepath.Walk(src, walker)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// CountLines Return the lines amount of the file
|
|
func CountLines(filename string) int {
|
|
var amount int
|
|
if strings.HasPrefix(filename, "~") {
|
|
filename, _ = homedir.Expand(filename)
|
|
}
|
|
file, err := os.Open(filename)
|
|
if err != nil {
|
|
return amount
|
|
}
|
|
defer file.Close()
|
|
|
|
scanner := bufio.NewScanner(file)
|
|
for scanner.Scan() {
|
|
val := strings.TrimSpace(scanner.Text())
|
|
if val == "" {
|
|
continue
|
|
}
|
|
amount++
|
|
}
|
|
|
|
if err := scanner.Err(); err != nil {
|
|
return amount
|
|
}
|
|
return amount
|
|
}
|
|
|
|
// CleanPath get environment variable
|
|
func CleanPath(raw string) string {
|
|
var out string
|
|
raw = NormalizePath(raw)
|
|
base := raw
|
|
if FileExists(base) {
|
|
base = filepath.Base(raw)
|
|
}
|
|
|
|
if strings.Count(base, "/") > 2 {
|
|
base = base[strings.LastIndex(base, "/")+1:]
|
|
if strings.TrimSpace(base) == "" {
|
|
domain, err := GetDomain(raw)
|
|
if err == nil {
|
|
base = domain
|
|
} else {
|
|
base = RandomString(8)
|
|
}
|
|
}
|
|
}
|
|
|
|
out = strings.ReplaceAll(base, "/", "_")
|
|
out = strings.ReplaceAll(out, ":", "_")
|
|
// DebugF("CleanPath: %s -- %s", raw, out)
|
|
return out
|
|
}
|
|
|
|
func IsFile(src string) bool {
|
|
fi, err := os.Stat(NormalizePath(src))
|
|
if err != nil {
|
|
return false
|
|
}
|
|
switch mode := fi.Mode(); {
|
|
case mode.IsDir():
|
|
return false
|
|
case mode.IsRegular():
|
|
if FileLength(src) > 0 {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
return false
|
|
}
|
|
|
|
func FolderLength(dir string) int {
|
|
dir = NormalizePath(dir)
|
|
var length int
|
|
if FileExists(dir) {
|
|
dir = path.Dir(dir)
|
|
}
|
|
files, err := os.ReadDir(dir)
|
|
if err != nil {
|
|
return length
|
|
}
|
|
length = len(files)
|
|
return length
|
|
}
|
|
|
|
// ImageAsBase64 read image file as a string
|
|
func ImageAsBase64(src string) string {
|
|
src = NormalizePath(src)
|
|
f, err := os.Open(src)
|
|
if err != nil {
|
|
ErrorF("File not found: %v", src)
|
|
return ""
|
|
}
|
|
|
|
// Read entire JPG into byte slice.
|
|
reader := bufio.NewReader(f)
|
|
content, err := io.ReadAll(reader)
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
// Encode as base64.
|
|
encoded := base64.StdEncoding.EncodeToString(content)
|
|
return encoded
|
|
}
|
|
|
|
// Base64Encode read image file as a string
|
|
func Base64Encode(raw string) string {
|
|
return base64.StdEncoding.EncodeToString([]byte(raw))
|
|
}
|
|
|
|
const bufSize = 1024
|
|
|
|
// OffsetRange represents a content block of a file.
|
|
type OffsetRange struct {
|
|
File string
|
|
Start int64
|
|
Stop int64
|
|
}
|
|
|
|
// SplitLineChunks splits file into chunks.
|
|
// The whole line are guaranteed to be split in the same chunk.
|
|
func SplitLineChunks(filename string, chunks int) ([]OffsetRange, error) {
|
|
info, err := os.Stat(filename)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if chunks <= 1 {
|
|
return []OffsetRange{
|
|
{
|
|
File: filename,
|
|
Start: 0,
|
|
Stop: info.Size(),
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
file, err := os.Open(filename)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer file.Close()
|
|
|
|
var ranges []OffsetRange
|
|
var offset int64
|
|
// avoid the last chunk too few bytes
|
|
preferSize := info.Size()/int64(chunks) + 1
|
|
for {
|
|
if offset+preferSize >= info.Size() {
|
|
ranges = append(ranges, OffsetRange{
|
|
File: filename,
|
|
Start: offset,
|
|
Stop: info.Size(),
|
|
})
|
|
break
|
|
}
|
|
|
|
offsetRange, err := nextRange(file, offset, offset+preferSize)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
ranges = append(ranges, offsetRange)
|
|
if offsetRange.Stop < info.Size() {
|
|
offset = offsetRange.Stop
|
|
} else {
|
|
break
|
|
}
|
|
}
|
|
|
|
return ranges, nil
|
|
}
|
|
|
|
func nextRange(file *os.File, start, stop int64) (OffsetRange, error) {
|
|
offset, err := skipPartialLine(file, stop)
|
|
if err != nil {
|
|
return OffsetRange{}, err
|
|
}
|
|
|
|
return OffsetRange{
|
|
File: file.Name(),
|
|
Start: start,
|
|
Stop: offset,
|
|
}, nil
|
|
}
|
|
|
|
func skipPartialLine(file *os.File, offset int64) (int64, error) {
|
|
for {
|
|
skipBuf := make([]byte, bufSize)
|
|
n, err := file.ReadAt(skipBuf, offset)
|
|
if err != nil && err != io.EOF {
|
|
return 0, err
|
|
}
|
|
if n == 0 {
|
|
return 0, io.EOF
|
|
}
|
|
|
|
for i := 0; i < n; i++ {
|
|
if skipBuf[i] != '\r' && skipBuf[i] != '\n' {
|
|
offset++
|
|
} else {
|
|
for ; i < n; i++ {
|
|
if skipBuf[i] == '\r' || skipBuf[i] == '\n' {
|
|
offset++
|
|
} else {
|
|
return offset, nil
|
|
}
|
|
}
|
|
return offset, nil
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// A RangeReader is used to read a range of content from a file.
|
|
type RangeReader struct {
|
|
file *os.File
|
|
start int64
|
|
stop int64
|
|
}
|
|
|
|
// NewRangeReader returns a RangeReader, which will read the range of content from file.
|
|
func NewRangeReader(file *os.File, start, stop int64) *RangeReader {
|
|
return &RangeReader{
|
|
file: file,
|
|
start: start,
|
|
stop: stop,
|
|
}
|
|
}
|
|
|
|
// Read reads the range of content into p.
|
|
func (rr *RangeReader) Read(p []byte) (n int, err error) {
|
|
stat, err := rr.file.Stat()
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
if rr.stop < rr.start || rr.start >= stat.Size() {
|
|
return 0, fmt.Errorf("exceed file size")
|
|
}
|
|
|
|
if rr.stop-rr.start < int64(len(p)) {
|
|
p = p[:rr.stop-rr.start]
|
|
}
|
|
|
|
n, err = rr.file.ReadAt(p, rr.start)
|
|
if err != nil {
|
|
return n, err
|
|
}
|
|
|
|
rr.start += int64(n)
|
|
return
|
|
}
|
|
|
|
func Move(src string, dest string) error {
|
|
src = NormalizePath(src)
|
|
if !IsFile(src) && DirLength(src) == 0 {
|
|
return fmt.Errorf("source does not exist: %v", src)
|
|
}
|
|
|
|
dest = NormalizePath(dest)
|
|
os.RemoveAll(dest)
|
|
DebugF("Moving %v --> %v", src, dest)
|
|
return os.Rename(src, dest)
|
|
}
|
|
|
|
func IsWritable(filename string) (isWritable bool, err error) {
|
|
isWritable = false
|
|
info, err := os.Stat(filename)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
err = nil
|
|
if !info.IsDir() {
|
|
return
|
|
}
|
|
|
|
// Check if the user write bit is enabled in file permission
|
|
if info.Mode().Perm()&0200 == 0 {
|
|
return
|
|
}
|
|
|
|
isWritable = true
|
|
return
|
|
}
|
|
|
|
// RenderText resolve template from signature file
|
|
func RenderText(format string, data map[string]string) string {
|
|
t := template.Must(template.New("").Parse(format))
|
|
buf := &bytes.Buffer{}
|
|
err := t.Execute(buf, data)
|
|
if err != nil {
|
|
return format
|
|
}
|
|
return buf.String()
|
|
}
|
|
|
|
// CopyFile attempts to copy a file from src to dst, first trying a hard link then falling back to a regular copy
|
|
func CopyFile(src, dst string) error {
|
|
// Normalize paths
|
|
src = NormalizePath(src)
|
|
dst = NormalizePath(dst)
|
|
|
|
// First try creating a hard link
|
|
if err := os.Link(src, dst); err == nil {
|
|
return nil
|
|
}
|
|
|
|
// If hard link fails, do a regular file copy
|
|
input, err := os.ReadFile(src)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to read source file: %v", err)
|
|
}
|
|
|
|
if err := os.WriteFile(dst, input, 0644); err != nil {
|
|
return fmt.Errorf("failed to write destination file: %v", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func GetRandString(length int) string {
|
|
const charset = "abcdefghijklmnopqrstuvwxyz0123456789"
|
|
// Create a new random source seeded with current timestamp
|
|
source := rand.NewSource(time.Now().UnixNano())
|
|
seededRand := rand.New(source)
|
|
|
|
b := make([]byte, length)
|
|
for i := range b {
|
|
b[i] = charset[seededRand.Intn(len(charset))]
|
|
}
|
|
return string(b)
|
|
}
|