starcrypto/filex/file.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)
}