312 lines
5.6 KiB
Go
312 lines
5.6 KiB
Go
|
|
package filex
|
||
|
|
|
||
|
|
import (
|
||
|
|
"bufio"
|
||
|
|
"errors"
|
||
|
|
"fmt"
|
||
|
|
"io"
|
||
|
|
"math/rand"
|
||
|
|
"os"
|
||
|
|
"path/filepath"
|
||
|
|
"regexp"
|
||
|
|
"sort"
|
||
|
|
"strconv"
|
||
|
|
"strings"
|
||
|
|
"time"
|
||
|
|
)
|
||
|
|
|
||
|
|
func Attach(src, dst, output string) error {
|
||
|
|
fpsrc, err := os.Open(src)
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
defer fpsrc.Close()
|
||
|
|
|
||
|
|
fpdst, err := os.Open(dst)
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
defer fpdst.Close()
|
||
|
|
|
||
|
|
fpout, err := os.Create(output)
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
defer fpout.Close()
|
||
|
|
|
||
|
|
if _, err := io.Copy(fpout, fpsrc); err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
if _, err := io.Copy(fpout, fpdst); err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func Detach(src string, bytenum int, dst1, dst2 string) error {
|
||
|
|
if bytenum < 0 {
|
||
|
|
return errors.New("bytenum must be non-negative")
|
||
|
|
}
|
||
|
|
|
||
|
|
fpsrc, err := os.Open(src)
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
defer fpsrc.Close()
|
||
|
|
|
||
|
|
fpdst1, err := os.Create(dst1)
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
defer fpdst1.Close()
|
||
|
|
|
||
|
|
fpdst2, err := os.Create(dst2)
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
defer fpdst2.Close()
|
||
|
|
|
||
|
|
if bytenum > 0 {
|
||
|
|
if _, err := io.CopyN(fpdst1, fpsrc, int64(bytenum)); err != nil && err != io.EOF {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if _, err := io.Copy(fpdst2, fpsrc); err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func SplitFile(src, dst string, num int, bynum bool, progress func(float64)) error {
|
||
|
|
if num <= 0 {
|
||
|
|
return errors.New("num must be greater than zero")
|
||
|
|
}
|
||
|
|
|
||
|
|
fpsrc, err := os.Open(src)
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
defer fpsrc.Close()
|
||
|
|
|
||
|
|
stat, err := fpsrc.Stat()
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
total := stat.Size()
|
||
|
|
if total == 0 {
|
||
|
|
return errors.New("file is empty")
|
||
|
|
}
|
||
|
|
|
||
|
|
var sizes []int64
|
||
|
|
if bynum {
|
||
|
|
if total < int64(num) {
|
||
|
|
return errors.New("file is too small to split")
|
||
|
|
}
|
||
|
|
base := total / int64(num)
|
||
|
|
rest := total % int64(num)
|
||
|
|
sizes = make([]int64, 0, num)
|
||
|
|
for i := 0; i < num; i++ {
|
||
|
|
sz := base
|
||
|
|
if int64(i) < rest {
|
||
|
|
sz++
|
||
|
|
}
|
||
|
|
sizes = append(sizes, sz)
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
chunk := int64(num)
|
||
|
|
for remain := total; remain > 0; {
|
||
|
|
sz := chunk
|
||
|
|
if remain < chunk {
|
||
|
|
sz = remain
|
||
|
|
}
|
||
|
|
sizes = append(sizes, sz)
|
||
|
|
remain -= sz
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
var copied int64
|
||
|
|
buf := make([]byte, 1024*1024)
|
||
|
|
for i, partSize := range sizes {
|
||
|
|
name := strings.Replace(dst, "*", fmt.Sprint(i), -1)
|
||
|
|
fpdst, err := os.Create(name)
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
|
||
|
|
remaining := partSize
|
||
|
|
for remaining > 0 {
|
||
|
|
readLen := int64(len(buf))
|
||
|
|
if remaining < readLen {
|
||
|
|
readLen = remaining
|
||
|
|
}
|
||
|
|
n, readErr := fpsrc.Read(buf[:readLen])
|
||
|
|
if n > 0 {
|
||
|
|
if _, err := fpdst.Write(buf[:n]); err != nil {
|
||
|
|
fpdst.Close()
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
remaining -= int64(n)
|
||
|
|
copied += int64(n)
|
||
|
|
reportProgress(progress, copied, total)
|
||
|
|
}
|
||
|
|
if readErr != nil {
|
||
|
|
if readErr == io.EOF && remaining == 0 {
|
||
|
|
break
|
||
|
|
}
|
||
|
|
fpdst.Close()
|
||
|
|
return readErr
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if err := fpdst.Close(); err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func MergeFile(src, dst string, progress func(float64)) error {
|
||
|
|
tmp := strings.Replace(src, "*", "0", -1)
|
||
|
|
dirEntries, err := os.ReadDir(filepath.Dir(tmp))
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
|
||
|
|
base := filepath.Base(src)
|
||
|
|
pattern := strings.Replace(base, "*", "(\\d+)", -1)
|
||
|
|
reg := regexp.MustCompile("^" + pattern + "$")
|
||
|
|
|
||
|
|
type indexedFile struct {
|
||
|
|
index int
|
||
|
|
name string
|
||
|
|
size int64
|
||
|
|
}
|
||
|
|
files := make([]indexedFile, 0)
|
||
|
|
var total int64
|
||
|
|
for _, entry := range dirEntries {
|
||
|
|
m := reg.FindStringSubmatch(entry.Name())
|
||
|
|
if len(m) != 2 {
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
idx, err := strconv.Atoi(m[1])
|
||
|
|
if err != nil {
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
info, err := entry.Info()
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
files = append(files, indexedFile{index: idx, name: entry.Name(), size: info.Size()})
|
||
|
|
total += info.Size()
|
||
|
|
}
|
||
|
|
if len(files) == 0 {
|
||
|
|
return errors.New("no split files found")
|
||
|
|
}
|
||
|
|
|
||
|
|
sort.Slice(files, func(i, j int) bool { return files[i].index < files[j].index })
|
||
|
|
|
||
|
|
fpdst, err := os.Create(dst)
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
defer fpdst.Close()
|
||
|
|
|
||
|
|
var copied int64
|
||
|
|
buf := make([]byte, 1024*1024)
|
||
|
|
for _, f := range files {
|
||
|
|
path := filepath.Join(filepath.Dir(tmp), f.name)
|
||
|
|
fpsrc, err := os.Open(path)
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
for {
|
||
|
|
n, readErr := fpsrc.Read(buf)
|
||
|
|
if n > 0 {
|
||
|
|
if _, err := fpdst.Write(buf[:n]); err != nil {
|
||
|
|
fpsrc.Close()
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
copied += int64(n)
|
||
|
|
reportProgress(progress, copied, total)
|
||
|
|
}
|
||
|
|
if readErr != nil {
|
||
|
|
if readErr == io.EOF {
|
||
|
|
break
|
||
|
|
}
|
||
|
|
fpsrc.Close()
|
||
|
|
return readErr
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if err := fpsrc.Close(); err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func FillWithRandom(path string, filesize, bufcap, bufnum int, progress func(float64)) error {
|
||
|
|
if filesize < 0 {
|
||
|
|
return errors.New("filesize must be non-negative")
|
||
|
|
}
|
||
|
|
if bufnum <= 0 {
|
||
|
|
bufnum = 1
|
||
|
|
}
|
||
|
|
if bufcap <= 0 {
|
||
|
|
bufcap = 1
|
||
|
|
}
|
||
|
|
if bufcap > filesize && filesize > 0 {
|
||
|
|
bufcap = filesize
|
||
|
|
}
|
||
|
|
|
||
|
|
rand.Seed(time.Now().UnixNano())
|
||
|
|
|
||
|
|
fp, err := os.Create(path)
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
defer fp.Close()
|
||
|
|
|
||
|
|
writer := bufio.NewWriter(fp)
|
||
|
|
defer writer.Flush()
|
||
|
|
|
||
|
|
if filesize == 0 {
|
||
|
|
reportProgress(progress, 0, 0)
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
pool := make([][]byte, 0, bufnum)
|
||
|
|
for i := 0; i < bufnum; i++ {
|
||
|
|
b := make([]byte, bufcap)
|
||
|
|
for j := 0; j < bufcap; j++ {
|
||
|
|
b[j] = byte(rand.Intn(256))
|
||
|
|
}
|
||
|
|
pool = append(pool, b)
|
||
|
|
}
|
||
|
|
|
||
|
|
written := 0
|
||
|
|
for written < filesize {
|
||
|
|
chunk := bufcap
|
||
|
|
if filesize-written < chunk {
|
||
|
|
chunk = filesize - written
|
||
|
|
}
|
||
|
|
buf := pool[rand.Intn(len(pool))][:chunk]
|
||
|
|
if _, err := writer.Write(buf); err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
written += chunk
|
||
|
|
reportProgress(progress, int64(written), int64(filesize))
|
||
|
|
}
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func reportProgress(progress func(float64), current, total int64) {
|
||
|
|
if progress == nil {
|
||
|
|
return
|
||
|
|
}
|
||
|
|
if total <= 0 {
|
||
|
|
progress(100)
|
||
|
|
return
|
||
|
|
}
|
||
|
|
progress(float64(current) / float64(total) * 100)
|
||
|
|
}
|