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) }