resolve merge conflict
This commit is contained in:
@ -6,6 +6,7 @@ go:
|
||||
- 1.12.x
|
||||
- 1.13.x
|
||||
- 1.14.x
|
||||
- 1.15.x
|
||||
env:
|
||||
- GO111MODULE=on
|
||||
script:
|
||||
|
@ -1,4 +1,4 @@
|
||||
# BT - Another Implementation Based On Golang [](https://travis-ci.org/xgfone/bt) [](https://pkg.go.dev/github.com/xgfone/bt) [](https://raw.githubusercontent.com/xgfone/bt/master/LICENSE)
|
||||
# BT - Another Implementation For Golang [](https://travis-ci.com/github/xgfone/bt) [](https://pkg.go.dev/github.com/xgfone/bt) [](https://raw.githubusercontent.com/xgfone/bt/master/LICENSE)
|
||||
|
||||
A pure golang implementation of [BitTorrent](http://bittorrent.org/beps/bep_0000.html) library, which is inspired by [dht](https://github.com/shiyanhui/dht) and [torrent](https://github.com/anacrolix/torrent).
|
||||
|
||||
|
@ -76,7 +76,7 @@ func newDHTServer(id metainfo.Hash, addr string, pm PeerManager) (s *Server, err
|
||||
conn, err := net.ListenPacket("udp", addr)
|
||||
if err == nil {
|
||||
c := Config{ID: id, PeerManager: pm, OnSearch: onSearch, OnTorrent: onTorrent}
|
||||
s = NewServer(conn, c)
|
||||
s = NewServer(conn, conn, c)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
@ -84,7 +84,7 @@ func Host(raddr net.Addr) string {
|
||||
func SplitHostPort(raddr net.Addr) (string, int) {
|
||||
var host, port, err = net.SplitHostPort(raddr.String())
|
||||
if err != nil {
|
||||
host = net.ParseIP(raddr.String()).String()
|
||||
//host = net.ParseIP(raddr.String()).String()
|
||||
port = strings.Replace(raddr.String(), ":", "", -1)
|
||||
}
|
||||
if host == "" {
|
||||
@ -275,8 +275,12 @@ func (a Address) MarshalBinary() (data []byte, err error) {
|
||||
|
||||
func (a *Address) decode(vs []interface{}) (err error) {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
err = e.(error)
|
||||
switch e := recover().(type) {
|
||||
case nil:
|
||||
case error:
|
||||
err = e
|
||||
default:
|
||||
err = fmt.Errorf("%v", e)
|
||||
}
|
||||
}()
|
||||
|
||||
@ -406,8 +410,12 @@ func (a HostAddress) Equal(o HostAddress) bool {
|
||||
|
||||
func (a *HostAddress) decode(vs []interface{}) (err error) {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
err = e.(error)
|
||||
switch e := recover().(type) {
|
||||
case nil:
|
||||
case error:
|
||||
err = e
|
||||
default:
|
||||
err = fmt.Errorf("%v", e)
|
||||
}
|
||||
}()
|
||||
|
||||
|
@ -101,7 +101,7 @@ func (f File) FilePieces(info Info) (fps FilePieces) {
|
||||
}}
|
||||
}
|
||||
|
||||
fps = make(FilePieces, 0, endPieceIndex-startPieceIndex)
|
||||
fps = make(FilePieces, 0, endPieceIndex-startPieceIndex+1)
|
||||
fps = append(fps, FilePiece{
|
||||
Index: uint32(startPieceIndex),
|
||||
Offset: uint32(startPieceOffset),
|
||||
|
@ -52,36 +52,56 @@ type Info struct {
|
||||
Files []File `json:"files,omitempty" bencode:"files,omitempty"` // BEP 3
|
||||
}
|
||||
|
||||
// NewInfoFromFilePath returns a new Info from a file or directory.
|
||||
func NewInfoFromFilePath(root string, pieceLength int64) (info Info, err error) {
|
||||
info.Name = filepath.Base(root)
|
||||
info.PieceLength = pieceLength
|
||||
err = filepath.Walk(root, func(path string, fi os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
// getAllInfoFiles returns the list of the all files in a certain directory
|
||||
// recursively.
|
||||
func getAllInfoFiles(rootDir string) (files []File, err error) {
|
||||
err = filepath.Walk(rootDir, func(path string, fi os.FileInfo, err error) error {
|
||||
if err != nil || fi.IsDir() {
|
||||
return err
|
||||
}
|
||||
|
||||
if path == root && !fi.IsDir() { // The root is a file.
|
||||
info.Length = fi.Size()
|
||||
return nil
|
||||
}
|
||||
|
||||
relPath, err := filepath.Rel(root, path)
|
||||
relPath, err := filepath.Rel(rootDir, path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error getting relative path: %s", err)
|
||||
}
|
||||
|
||||
info.Files = append(info.Files, File{
|
||||
Paths: strings.Split(relPath, string(filepath.Separator)),
|
||||
paths := strings.Split(relPath, string(filepath.Separator))
|
||||
for _, name := range paths {
|
||||
if name == ".git" {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
files = append(files, File{
|
||||
Paths: paths,
|
||||
Length: fi.Size(),
|
||||
})
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err == nil {
|
||||
if err == nil && len(files) == 0 {
|
||||
err = fmt.Errorf("no files in the directory '%s'", rootDir)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// NewInfoFromFilePath returns a new Info from a file or directory.
|
||||
func NewInfoFromFilePath(root string, pieceLength int64) (info Info, err error) {
|
||||
root = filepath.Clean(root)
|
||||
|
||||
fi, err := os.Stat(root)
|
||||
if err != nil {
|
||||
return
|
||||
} else if !fi.IsDir() {
|
||||
info.Length = fi.Size()
|
||||
} else if info.Files, err = getAllInfoFiles(root); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
sort.Sort(files(info.Files))
|
||||
info.Pieces, err = GeneratePiecesFromFiles(info.AllFiles(), info.PieceLength,
|
||||
info.Pieces, err = GeneratePiecesFromFiles(info.AllFiles(), pieceLength,
|
||||
func(file File) (io.ReadCloser, error) {
|
||||
if _len := len(file.Paths); _len > 0 {
|
||||
paths := make([]string, 0, _len+1)
|
||||
@ -92,10 +112,12 @@ func NewInfoFromFilePath(root string, pieceLength int64) (info Info, err error)
|
||||
return os.Open(root)
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
if err == nil {
|
||||
info.Name = filepath.Base(root)
|
||||
info.PieceLength = pieceLength
|
||||
} else {
|
||||
err = fmt.Errorf("error generating pieces: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
@ -127,19 +149,33 @@ func (info Info) PieceOffset(index, offset uint32) int64 {
|
||||
}
|
||||
|
||||
// GetFileByOffset returns the file and its offset by the total offset.
|
||||
//
|
||||
// If fileOffset is eqaul to file.Length, it means to reach the end.
|
||||
func (info Info) GetFileByOffset(offset int64) (file File, fileOffset int64) {
|
||||
if !info.IsDir() {
|
||||
return File{Length: info.Length}, offset
|
||||
if offset > info.Length {
|
||||
panic(fmt.Errorf("offset '%d' exceeds the maximum length '%d'",
|
||||
offset, info.Length))
|
||||
}
|
||||
return File{Length: info.Length, Paths: []string{info.Name}}, offset
|
||||
}
|
||||
|
||||
fileOffset = offset
|
||||
for _, file = range info.Files {
|
||||
for i, _len := 0, len(info.Files)-1; i <= _len; i++ {
|
||||
file = info.Files[i]
|
||||
if fileOffset < file.Length {
|
||||
return
|
||||
} else if fileOffset == file.Length && i == _len {
|
||||
return
|
||||
}
|
||||
fileOffset -= file.Length
|
||||
}
|
||||
|
||||
if fileOffset > file.Length {
|
||||
panic(fmt.Errorf("offset '%d' exceeds the maximum length '%d'",
|
||||
offset, info.TotalLength()))
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
|
98
metainfo/info_test.go
Normal file
98
metainfo/info_test.go
Normal file
@ -0,0 +1,98 @@
|
||||
// Copyright 2020 xgfone
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package metainfo
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestInfo_GetFileByOffset(t *testing.T) {
|
||||
FileInfo := Info{
|
||||
Name: "test_rw",
|
||||
PieceLength: 64,
|
||||
Length: 600,
|
||||
}
|
||||
file, fileOffset := FileInfo.GetFileByOffset(0)
|
||||
if file.Offset(FileInfo) != 0 || fileOffset != 0 {
|
||||
t.Errorf("expect fileOffset='%d', but got '%d'", 0, fileOffset)
|
||||
}
|
||||
file, fileOffset = FileInfo.GetFileByOffset(100)
|
||||
if file.Offset(FileInfo) != 0 || fileOffset != 100 {
|
||||
t.Errorf("expect fileOffset='%d', but got '%d'", 100, fileOffset)
|
||||
}
|
||||
file, fileOffset = FileInfo.GetFileByOffset(600)
|
||||
if file.Offset(FileInfo) != 0 || fileOffset != 600 {
|
||||
t.Errorf("expect fileOffset='%d', but got '%d'", 600, fileOffset)
|
||||
}
|
||||
|
||||
DirInfo := Info{
|
||||
Name: "test_rw",
|
||||
PieceLength: 64,
|
||||
Files: []File{
|
||||
{Length: 100, Paths: []string{"file1"}},
|
||||
{Length: 200, Paths: []string{"file2"}},
|
||||
{Length: 300, Paths: []string{"file3"}},
|
||||
},
|
||||
}
|
||||
file, fileOffset = DirInfo.GetFileByOffset(0)
|
||||
if file.Offset(DirInfo) != 0 || file.Length != 100 || fileOffset != 0 {
|
||||
t.Errorf("expect fileOffset='%d', but got '%d'", 0, fileOffset)
|
||||
}
|
||||
file, fileOffset = DirInfo.GetFileByOffset(50)
|
||||
if file.Offset(DirInfo) != 0 || file.Length != 100 || fileOffset != 50 {
|
||||
t.Errorf("expect fileOffset='%d', but got '%d'", 50, fileOffset)
|
||||
}
|
||||
file, fileOffset = DirInfo.GetFileByOffset(100)
|
||||
if file.Offset(DirInfo) != 100 || file.Length != 200 || fileOffset != 0 {
|
||||
t.Errorf("expect fileOffset='%d', but got '%d'", 0, fileOffset)
|
||||
}
|
||||
file, fileOffset = DirInfo.GetFileByOffset(200)
|
||||
if file.Offset(DirInfo) != 100 || file.Length != 200 || fileOffset != 100 {
|
||||
t.Errorf("expect fileOffset='%d', but got '%d'", 100, fileOffset)
|
||||
}
|
||||
file, fileOffset = DirInfo.GetFileByOffset(300)
|
||||
if file.Offset(DirInfo) != 300 || file.Length != 300 || fileOffset != 0 {
|
||||
t.Errorf("expect fileOffset='%d', but got '%d'", 0, fileOffset)
|
||||
}
|
||||
file, fileOffset = DirInfo.GetFileByOffset(400)
|
||||
if file.Offset(DirInfo) != 300 || file.Length != 300 || fileOffset != 100 {
|
||||
t.Errorf("expect fileOffset='%d', but got '%d'", 100, fileOffset)
|
||||
}
|
||||
file, fileOffset = DirInfo.GetFileByOffset(600)
|
||||
if file.Offset(DirInfo) != 300 || file.Length != 300 || fileOffset != 300 {
|
||||
t.Errorf("expect fileOffset='%d', but got '%d'", 300, fileOffset)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewInfoFromFilePath(t *testing.T) {
|
||||
info, err := NewInfoFromFilePath("info.go", PieceSize256KB)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
} else if info.Name != "info.go" || info.Files != nil {
|
||||
t.Errorf("invalid info %+v\n", info)
|
||||
}
|
||||
|
||||
info, err = NewInfoFromFilePath("../metainfo", PieceSize256KB)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
} else if info.Name != "metainfo" || info.Files == nil || info.Length > 0 {
|
||||
t.Errorf("invalid info %+v\n", info)
|
||||
}
|
||||
|
||||
info, err = NewInfoFromFilePath("../../bt", PieceSize256KB)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
} else if info.Name != "bt" || info.Files == nil || info.Length > 0 {
|
||||
t.Errorf("invalid info %+v\n", info)
|
||||
}
|
||||
}
|
@ -20,7 +20,9 @@ import (
|
||||
"crypto/sha1"
|
||||
"encoding/base32"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/xgfone/bt/bencode"
|
||||
)
|
||||
@ -96,6 +98,26 @@ func (h Hash) IsZero() bool {
|
||||
return h == zeroHash
|
||||
}
|
||||
|
||||
// WriteBinary is the same as MarshalBinary, but writes the result into w
|
||||
// instead of returning.
|
||||
func (h Hash) WriteBinary(w io.Writer) (m int, err error) {
|
||||
return w.Write(h[:])
|
||||
}
|
||||
|
||||
// UnmarshalBinary implements the interface binary.BinaryUnmarshaler.
|
||||
func (h *Hash) UnmarshalBinary(b []byte) (err error) {
|
||||
if len(b) < HashSize {
|
||||
return errors.New("Hash.UnmarshalBinary: too few bytes")
|
||||
}
|
||||
copy((*h)[:], b[:HashSize])
|
||||
return
|
||||
}
|
||||
|
||||
// MarshalBinary implements the interface binary.BinaryMarshaler.
|
||||
func (h Hash) MarshalBinary() (data []byte, err error) {
|
||||
return h[:], nil
|
||||
}
|
||||
|
||||
// MarshalBencode implements the interface bencode.Marshaler.
|
||||
func (h Hash) MarshalBencode() (b []byte, err error) {
|
||||
return bencode.EncodeBytes(h[:])
|
||||
|
80
metainfo/infohash_test.go
Normal file
80
metainfo/infohash_test.go
Normal file
@ -0,0 +1,80 @@
|
||||
// Copyright 2020 xgfone
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package metainfo
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestHash(t *testing.T) {
|
||||
hexHash := "0001020304050607080909080706050403020100"
|
||||
|
||||
b, err := NewHashFromHexString(hexHash).MarshalBencode()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var h Hash
|
||||
if err = h.UnmarshalBencode(b); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if hexs := h.String(); hexs != hexHash {
|
||||
t.Errorf("expect '%s', but got '%s'\n", hexHash, hexs)
|
||||
}
|
||||
|
||||
h = Hash{}
|
||||
hexHash = "0001020304050607080900010203040506070809"
|
||||
err = h.UnmarshalBinary([]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9})
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
} else if hexs := h.HexString(); hexs != hexHash {
|
||||
t.Errorf("expect '%s', but got '%s'\n", hexHash, hexs)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHashes(t *testing.T) {
|
||||
hexHash1 := "0101010101010101010101010101010101010101"
|
||||
hexHash2 := "0202020202020202020202020202020202020202"
|
||||
|
||||
hashes := Hashes{
|
||||
NewHashFromHexString(hexHash1),
|
||||
NewHashFromHexString(hexHash2),
|
||||
}
|
||||
|
||||
b, err := hashes.MarshalBencode()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
hashes = Hashes{}
|
||||
if err = hashes.UnmarshalBencode(b); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if _len := len(hashes); _len != 2 {
|
||||
t.Fatalf("expect the len(hashes)==2, but got '%d'", _len)
|
||||
}
|
||||
|
||||
for i, h := range hashes {
|
||||
if i == 0 {
|
||||
if hexs := h.HexString(); hexs != hexHash1 {
|
||||
t.Errorf("index %d: expect '%s', but got '%s'\n", i, hexHash1, hexs)
|
||||
}
|
||||
} else {
|
||||
if hexs := h.HexString(); hexs != hexHash2 {
|
||||
t.Errorf("index %d: expect '%s', but got '%s'\n", i, hexHash2, hexs)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -69,16 +69,14 @@ func (r *reader) ReadAt(p []byte, offset int64) (n int, err error) {
|
||||
|
||||
for _len := len(p); n < _len; {
|
||||
file, fileOffset := r.info.GetFileByOffset(offset)
|
||||
if file.Length == 0 {
|
||||
if file.Length == 0 || file.Length == fileOffset {
|
||||
err = io.EOF
|
||||
break
|
||||
}
|
||||
|
||||
length := int(file.Length-fileOffset) + n
|
||||
if _len < length {
|
||||
length = _len
|
||||
} else if length <= n {
|
||||
err = io.EOF
|
||||
break
|
||||
pend := n + int(file.Length-fileOffset)
|
||||
if pend > _len {
|
||||
pend = _len
|
||||
}
|
||||
|
||||
filename := file.PathWithPrefix(r.root, r.info)
|
||||
@ -86,7 +84,7 @@ func (r *reader) ReadAt(p []byte, offset int64) (n int, err error) {
|
||||
break
|
||||
}
|
||||
|
||||
m, err = f.ReadAt(p[n:length], fileOffset)
|
||||
m, err = f.ReadAt(p[n:pend], fileOffset)
|
||||
n += m
|
||||
offset += int64(m)
|
||||
if err != nil {
|
||||
|
78
metainfo/reader_writer_test.go
Normal file
78
metainfo/reader_writer_test.go
Normal file
@ -0,0 +1,78 @@
|
||||
// Copyright 2020 xgfone
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package metainfo
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func generateTestPieceData(len int, numData byte) []byte {
|
||||
b := make([]byte, len)
|
||||
for i := 0; i < len; i++ {
|
||||
b[i] = numData
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func TestWriterAndReader(t *testing.T) {
|
||||
info := Info{
|
||||
Name: "test_rw",
|
||||
PieceLength: 64,
|
||||
Files: []File{
|
||||
{Length: 100, Paths: []string{"file1"}},
|
||||
{Length: 200, Paths: []string{"file2"}},
|
||||
{Length: 300, Paths: []string{"file3"}},
|
||||
},
|
||||
}
|
||||
|
||||
r := NewReader("", info)
|
||||
w := NewWriter("", info, 0600)
|
||||
defer func() {
|
||||
w.Close()
|
||||
r.Close()
|
||||
os.RemoveAll("test_rw")
|
||||
}()
|
||||
|
||||
datalen := 600
|
||||
wdata := make([]byte, datalen)
|
||||
for i := 0; i < datalen; i++ {
|
||||
wdata[i] = byte(i%26 + 97)
|
||||
}
|
||||
|
||||
n, err := w.WriteAt(wdata, 0)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
} else if n != len(wdata) {
|
||||
t.Errorf("expect wrote '%d', but got '%d'\n", len(wdata), n)
|
||||
return
|
||||
}
|
||||
|
||||
rdata := make([]byte, datalen)
|
||||
n, err = r.ReadAt(rdata, 0)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
} else if n != len(rdata) {
|
||||
t.Errorf("expect read '%d', but got '%d'\n", len(rdata), n)
|
||||
return
|
||||
}
|
||||
|
||||
if bytes.Compare(rdata, wdata) != 0 {
|
||||
t.Errorf("expect read '%x', but got '%x'\n", wdata, rdata)
|
||||
}
|
||||
}
|
@ -40,12 +40,12 @@ type writer struct {
|
||||
|
||||
// NewWriter returns a new Writer.
|
||||
//
|
||||
// If fileMode is equal to 0, it is 0700 by default.
|
||||
// If fileMode is equal to 0, it is 0600 by default.
|
||||
//
|
||||
// Notice: fileMode is only used when writing the data.
|
||||
func NewWriter(rootDir string, info Info, fileMode os.FileMode) Writer {
|
||||
if fileMode == 0 {
|
||||
fileMode = 0700
|
||||
fileMode = 0600
|
||||
}
|
||||
|
||||
return &writer{
|
||||
@ -63,13 +63,8 @@ func (w *writer) open(filename string) (f *os.File, err error) {
|
||||
|
||||
f, ok := w.files[filename]
|
||||
if !ok {
|
||||
mode := w.mode
|
||||
if mode == 0 {
|
||||
mode = 0700
|
||||
}
|
||||
|
||||
if err = os.MkdirAll(filepath.Dir(filename), mode); err == nil {
|
||||
if f, err = os.OpenFile(filename, wflag, 0700); err == nil {
|
||||
if err = os.MkdirAll(filepath.Dir(filename), 0700); err == nil {
|
||||
if f, err = os.OpenFile(filename, wflag, w.mode); err == nil {
|
||||
w.files[filename] = f
|
||||
}
|
||||
}
|
||||
@ -103,7 +98,8 @@ func (w *writer) WriteAt(p []byte, offset int64) (n int, err error) {
|
||||
|
||||
for _len := len(p); n < _len; {
|
||||
file, fileOffset := w.info.GetFileByOffset(offset)
|
||||
if file.Length == 0 {
|
||||
if file.Length == 0 || file.Length == fileOffset {
|
||||
err = io.ErrShortWrite
|
||||
break
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user