refactor: split into subpackages and add AEAD/options/stream APIs with comprehensive tests
This commit is contained in:
+311
@@ -0,0 +1,311 @@
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user