mirror of
https://github.com/rumanzo/bt2qbt.git
synced 2024-11-25 11:52:56 +01:00
Fixstructures (#39)
* Fix structures * torrents v2 file tree interface * get files vrom both v1 and v2 torrents * fix typo * style * cleanup makefile * new test fileset with real qBittorrent files and new tests * counting files using torrent file * Handle v2 and hybryd torrents
This commit is contained in:
parent
b66b5f8e75
commit
47e0028bc1
14
Makefile
14
Makefile
@ -1,11 +1,4 @@
|
||||
#GOOS=windows GOACH=amd64 go build -o bt2qbt_v${1}_amd64.exe -tags forceposix
|
||||
#GOOS=windows GOARCH=386 go build -o bt2qbt_v${1}_i386.exe -tags forceposix
|
||||
#GOOS=linux GOARCH=amd64 go build -o bt2qbt_v${1}_amd64_linux -tags forceposix
|
||||
#GOOS=linux GOARCH=386 go build -o bt2qbt_v${1}_i386_linux -tags forceposix
|
||||
#GOOS=darwin GOARCH=amd64 go build -o bt2qbt_v${1}_amd64_macos -tags forceposix
|
||||
#GOOS=darwin GOARCH=386 go build -o bt2qbt_v${1}_i386_macos -tags forceposix
|
||||
|
||||
gotag=1.18.0-bullseye
|
||||
gotag=1.18.1-bullseye
|
||||
|
||||
commit=$(shell git rev-parse HEAD)
|
||||
|
||||
@ -15,13 +8,12 @@ buildenvs = -e CGO_ENABLED=0
|
||||
version = 1.999
|
||||
ldflags = -ldflags="-X 'main.version=$(version)' -X 'main.commit=$(commit)' -X 'main.buildImage=golang:$(gotag)'"
|
||||
|
||||
all: tests build
|
||||
all: | tests build
|
||||
|
||||
tests:
|
||||
$(dockercmd) golang:$(gotag) go test $(buildtags) ./...
|
||||
|
||||
build: | tests windows linux darwin
|
||||
|
||||
build: windows linux darwin
|
||||
|
||||
windows:
|
||||
$(dockercmd) $(buildenvs) -e GOOS=windows -e GOARCH=amd64 golang:$(gotag) go build -v $(buildtags) $(ldflags) -o bt2qbt_v$(version)_amd64.exe
|
||||
|
@ -17,9 +17,10 @@ func (transfer *TransferStructure) HandleStructures() {
|
||||
transfer.Fastresume.ActiveTime = transfer.ResumeItem.Runtime
|
||||
transfer.Fastresume.AddedTime = transfer.ResumeItem.AddedOn
|
||||
transfer.Fastresume.CompletedTime = transfer.ResumeItem.CompletedOn
|
||||
transfer.Fastresume.Info = transfer.TorrentFile.Info
|
||||
transfer.Fastresume.Info = transfer.TorrentFileRaw["info"]
|
||||
transfer.Fastresume.InfoHash = transfer.ResumeItem.Info
|
||||
transfer.Fastresume.SeedingTime = transfer.ResumeItem.Runtime
|
||||
transfer.HandlePriority() // handle priorities before handling pieces and state
|
||||
transfer.HandleState()
|
||||
transfer.Fastresume.FinishedTime = int64(time.Since(time.Unix(transfer.ResumeItem.CompletedOn, 0)).Minutes())
|
||||
|
||||
@ -30,7 +31,6 @@ func (transfer *TransferStructure) HandleStructures() {
|
||||
transfer.HandleLabels()
|
||||
|
||||
transfer.HandleTrackers()
|
||||
transfer.HandlePriority() // important handle priorities before handling pieces
|
||||
|
||||
/*
|
||||
pieces maps to a string whose length is a multiple of 20. It is to be subdivided into strings of length 20,
|
||||
|
@ -87,20 +87,19 @@ func (transfer *TransferStructure) HandleState() {
|
||||
transfer.Fastresume.Paused = 1
|
||||
transfer.Fastresume.AutoManaged = 0
|
||||
} else {
|
||||
if transfer.TorrentFile.Info.Files != nil {
|
||||
if len(transfer.TorrentFile.Info.Files) > 1 {
|
||||
var parted bool
|
||||
for _, prio := range transfer.ResumeItem.Prio {
|
||||
if byte(prio) == 0 || byte(prio) == 128 {
|
||||
parted = true
|
||||
}
|
||||
}
|
||||
if parted {
|
||||
transfer.Fastresume.Paused = 1
|
||||
transfer.Fastresume.AutoManaged = 0
|
||||
return
|
||||
if len(transfer.TorrentFile.GetFileList()) > 1 {
|
||||
var parted bool
|
||||
for _, prio := range transfer.Fastresume.FilePriority {
|
||||
if prio == 0 {
|
||||
parted = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if parted {
|
||||
transfer.Fastresume.Paused = 1
|
||||
transfer.Fastresume.AutoManaged = 0
|
||||
return
|
||||
}
|
||||
}
|
||||
transfer.Fastresume.Paused = 0
|
||||
transfer.Fastresume.AutoManaged = 1
|
||||
@ -170,6 +169,13 @@ func (transfer *TransferStructure) HandleTrackers() {
|
||||
}
|
||||
|
||||
func (transfer *TransferStructure) HandlePriority() {
|
||||
if transfer.TorrentFile.IsV2OrHybryd() { // so we need get only odd
|
||||
trimmedPrio := make([]byte, 0, len(transfer.ResumeItem.Prio)/2)
|
||||
for i := 0; i < len(transfer.ResumeItem.Prio); i += 2 {
|
||||
trimmedPrio = append(trimmedPrio, transfer.ResumeItem.Prio[i])
|
||||
}
|
||||
transfer.ResumeItem.Prio = trimmedPrio
|
||||
}
|
||||
for _, c := range transfer.ResumeItem.Prio {
|
||||
if i := int(c); (i == 0) || (i == 128) { // if priority not selected
|
||||
transfer.Fastresume.FilePriority = append(transfer.Fastresume.FilePriority, 0)
|
||||
@ -195,7 +201,7 @@ func (transfer *TransferStructure) HandlePieces() {
|
||||
if transfer.Fastresume.Unfinished != nil {
|
||||
transfer.FillWholePieces(0)
|
||||
} else {
|
||||
if len(transfer.TorrentFile.Info.Files) > 0 {
|
||||
if len(transfer.TorrentFile.GetFileList()) > 0 {
|
||||
transfer.FillPiecesParted()
|
||||
} else {
|
||||
transfer.FillWholePieces(1)
|
||||
@ -220,7 +226,7 @@ func (transfer *TransferStructure) FillPiecesParted() {
|
||||
}
|
||||
var fileOffsets []Offset
|
||||
bytesLength := int64(0)
|
||||
for _, file := range transfer.TorrentFile.Info.Files {
|
||||
for _, file := range transfer.TorrentFile.GetFileListWB() { // need to adapt for torrents v2 version
|
||||
fileFirstOffset := bytesLength + 1
|
||||
bytesLength += file.Length
|
||||
fileLastOffset := bytesLength
|
||||
@ -272,7 +278,7 @@ func (transfer *TransferStructure) HandleSavePaths() {
|
||||
}
|
||||
lastPathName := fileHelpers.Base(transfer.ResumeItem.Path)
|
||||
|
||||
if len(transfer.TorrentFile.Info.Files) > 0 {
|
||||
if len(transfer.TorrentFile.GetFileList()) > 0 {
|
||||
if lastPathName == torrentName {
|
||||
transfer.Fastresume.QBtContentLayout = "Original"
|
||||
transfer.Fastresume.QbtSavePath = fileHelpers.CutLastPath(transfer.ResumeItem.Path, transfer.Opts.PathSeparator)
|
||||
@ -300,14 +306,8 @@ func (transfer *TransferStructure) HandleSavePaths() {
|
||||
transfer.Fastresume.QBtContentLayout = "NoSubfolder"
|
||||
// NoSubfolder always has full mapped files
|
||||
// so we append all of them
|
||||
for _, filePath := range transfer.TorrentFile.Info.Files {
|
||||
var paths []string
|
||||
if len(filePath.PathUTF8) != 0 {
|
||||
paths = filePath.PathUTF8
|
||||
} else {
|
||||
paths = filePath.Path
|
||||
}
|
||||
transfer.Fastresume.MappedFiles = append(transfer.Fastresume.MappedFiles, fileHelpers.Join(paths, transfer.Opts.PathSeparator))
|
||||
for _, filePath := range transfer.TorrentFile.GetFileList() {
|
||||
transfer.Fastresume.MappedFiles = append(transfer.Fastresume.MappedFiles, fileHelpers.Normalize(filePath, transfer.Opts.PathSeparator))
|
||||
}
|
||||
// and then doing remap if resumeItem contain target field
|
||||
if maxIndex := transfer.FindHighestIndexOfMappedFiles(); maxIndex >= 0 {
|
||||
|
@ -921,11 +921,11 @@ func TestTransferStructure_HandlePieces(t *testing.T) {
|
||||
TorrentFile: &torrentStructures.Torrent{
|
||||
Info: &torrentStructures.TorrentInfo{
|
||||
Files: []*torrentStructures.TorrentFile{
|
||||
&torrentStructures.TorrentFile{Length: 5},
|
||||
&torrentStructures.TorrentFile{Length: 5},
|
||||
&torrentStructures.TorrentFile{Length: 5},
|
||||
&torrentStructures.TorrentFile{Length: 5},
|
||||
&torrentStructures.TorrentFile{Length: 5},
|
||||
&torrentStructures.TorrentFile{Path: []string{`/`}, Length: 5},
|
||||
&torrentStructures.TorrentFile{Path: []string{`/`}, Length: 5},
|
||||
&torrentStructures.TorrentFile{Path: []string{`/`}, Length: 5},
|
||||
&torrentStructures.TorrentFile{Path: []string{`/`}, Length: 5},
|
||||
&torrentStructures.TorrentFile{Path: []string{`/`}, Length: 5},
|
||||
},
|
||||
PieceLength: 5,
|
||||
},
|
||||
@ -954,9 +954,9 @@ func TestTransferStructure_HandlePieces(t *testing.T) {
|
||||
TorrentFile: &torrentStructures.Torrent{
|
||||
Info: &torrentStructures.TorrentInfo{
|
||||
Files: []*torrentStructures.TorrentFile{
|
||||
&torrentStructures.TorrentFile{Length: 13},
|
||||
&torrentStructures.TorrentFile{Length: 7},
|
||||
&torrentStructures.TorrentFile{Length: 5},
|
||||
&torrentStructures.TorrentFile{Path: []string{`/`}, Length: 13},
|
||||
&torrentStructures.TorrentFile{Path: []string{`/`}, Length: 7},
|
||||
&torrentStructures.TorrentFile{Path: []string{`/`}, Length: 5},
|
||||
},
|
||||
PieceLength: 5, // 25 total
|
||||
},
|
||||
@ -985,9 +985,9 @@ func TestTransferStructure_HandlePieces(t *testing.T) {
|
||||
TorrentFile: &torrentStructures.Torrent{
|
||||
Info: &torrentStructures.TorrentInfo{
|
||||
Files: []*torrentStructures.TorrentFile{
|
||||
&torrentStructures.TorrentFile{Length: 9},
|
||||
&torrentStructures.TorrentFile{Length: 6},
|
||||
&torrentStructures.TorrentFile{Length: 10},
|
||||
&torrentStructures.TorrentFile{Path: []string{`/`}, Length: 9},
|
||||
&torrentStructures.TorrentFile{Path: []string{`/`}, Length: 6},
|
||||
&torrentStructures.TorrentFile{Path: []string{`/`}, Length: 10},
|
||||
},
|
||||
PieceLength: 5, // 25 total
|
||||
},
|
||||
@ -1016,9 +1016,9 @@ func TestTransferStructure_HandlePieces(t *testing.T) {
|
||||
TorrentFile: &torrentStructures.Torrent{
|
||||
Info: &torrentStructures.TorrentInfo{
|
||||
Files: []*torrentStructures.TorrentFile{
|
||||
&torrentStructures.TorrentFile{Length: 13},
|
||||
&torrentStructures.TorrentFile{Length: 7},
|
||||
&torrentStructures.TorrentFile{Length: 5},
|
||||
&torrentStructures.TorrentFile{Path: []string{`/`}, Length: 13},
|
||||
&torrentStructures.TorrentFile{Path: []string{`/`}, Length: 7},
|
||||
&torrentStructures.TorrentFile{Path: []string{`/`}, Length: 5},
|
||||
},
|
||||
PieceLength: 5, // 25 total
|
||||
},
|
||||
@ -1102,25 +1102,93 @@ func TestTransferStructure_HandlePieces(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestTransferStructure_HandlePriority(t *testing.T) {
|
||||
transferStructure := TransferStructure{
|
||||
Fastresume: &qBittorrentStructures.QBittorrentFastresume{FilePriority: []int64{}},
|
||||
ResumeItem: &utorrentStructs.ResumeItem{
|
||||
Prio: []byte{
|
||||
byte(0),
|
||||
byte(128),
|
||||
byte(2),
|
||||
byte(5),
|
||||
byte(8),
|
||||
byte(9),
|
||||
byte(15),
|
||||
byte(127), // unexpected
|
||||
|
||||
type HandlePriorityCase struct {
|
||||
name string
|
||||
mustFail bool
|
||||
newTransferStructure *TransferStructure
|
||||
expected []int64
|
||||
}
|
||||
cases := []HandlePriorityCase{
|
||||
{
|
||||
name: "001 mustfail",
|
||||
newTransferStructure: &TransferStructure{
|
||||
Fastresume: &qBittorrentStructures.QBittorrentFastresume{FilePriority: []int64{}},
|
||||
TorrentFile: &torrentStructures.Torrent{Info: &torrentStructures.TorrentInfo{}},
|
||||
ResumeItem: &utorrentStructs.ResumeItem{
|
||||
Prio: []byte{},
|
||||
},
|
||||
},
|
||||
mustFail: true,
|
||||
expected: []int64{0, 0, 1, 1, 1, 6, 6, 0},
|
||||
},
|
||||
{
|
||||
name: "002 check priotiry for v1 torrents",
|
||||
newTransferStructure: &TransferStructure{
|
||||
Fastresume: &qBittorrentStructures.QBittorrentFastresume{FilePriority: []int64{}},
|
||||
TorrentFile: &torrentStructures.Torrent{Info: &torrentStructures.TorrentInfo{}},
|
||||
ResumeItem: &utorrentStructs.ResumeItem{
|
||||
Prio: []byte{
|
||||
byte(0),
|
||||
byte(128),
|
||||
byte(2),
|
||||
byte(5),
|
||||
byte(8),
|
||||
byte(9),
|
||||
byte(15),
|
||||
byte(127), // unexpected
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: []int64{0, 0, 1, 1, 1, 6, 6, 0},
|
||||
},
|
||||
{
|
||||
name: "002 check priotiry for v2 torrents",
|
||||
newTransferStructure: &TransferStructure{
|
||||
Fastresume: &qBittorrentStructures.QBittorrentFastresume{FilePriority: []int64{}},
|
||||
TorrentFile: &torrentStructures.Torrent{
|
||||
Info: &torrentStructures.TorrentInfo{
|
||||
FileTree: map[string]interface{}{},
|
||||
},
|
||||
},
|
||||
ResumeItem: &utorrentStructs.ResumeItem{
|
||||
Prio: []byte{
|
||||
byte(0),
|
||||
byte(128),
|
||||
byte(128),
|
||||
byte(128),
|
||||
byte(2),
|
||||
byte(128),
|
||||
byte(5),
|
||||
byte(128),
|
||||
byte(8),
|
||||
byte(128),
|
||||
byte(9),
|
||||
byte(128),
|
||||
byte(15),
|
||||
byte(128),
|
||||
byte(127), // unexpected
|
||||
byte(128),
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: []int64{0, 0, 1, 1, 1, 6, 6, 0},
|
||||
},
|
||||
}
|
||||
expect := []int64{0, 0, 1, 1, 1, 6, 6, 0}
|
||||
transferStructure.HandlePriority()
|
||||
if !reflect.DeepEqual(transferStructure.Fastresume.FilePriority, expect) {
|
||||
t.Fatalf("Unexpected error: opts isn't equal:\n Got: %#v\n Expect %#v\n", transferStructure.Fastresume.FilePriority, expect)
|
||||
for _, testCase := range cases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
testCase.newTransferStructure.HandlePriority()
|
||||
equal := reflect.DeepEqual(testCase.expected, testCase.newTransferStructure.Fastresume.FilePriority)
|
||||
if !equal && !testCase.mustFail {
|
||||
changes, err := diff.Diff(testCase.newTransferStructure.Fastresume.FilePriority, testCase.expected, diff.DiscardComplexOrigin())
|
||||
if err != nil {
|
||||
t.Error(err.Error())
|
||||
}
|
||||
t.Fatalf("Unexpected error: opts isn't equal:\n Got: %#v\n Expect %#v\n Diff: %v\n", testCase.newTransferStructure.Fastresume.FilePriority, testCase.expected, spew.Sdump(changes))
|
||||
} else if equal && testCase.mustFail {
|
||||
t.Fatalf("Unexpected error: structures are equal, but they shouldn't\n Got: %v\n", spew.Sdump(testCase.newTransferStructure.Fastresume.FilePriority))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -1239,22 +1307,17 @@ func TestTransferStructure_HandleState(t *testing.T) {
|
||||
Fastresume: &qBittorrentStructures.QBittorrentFastresume{},
|
||||
ResumeItem: &utorrentStructs.ResumeItem{
|
||||
Started: 1,
|
||||
Prio: []byte{
|
||||
byte(1),
|
||||
byte(1),
|
||||
byte(2),
|
||||
byte(5),
|
||||
byte(8),
|
||||
byte(9),
|
||||
byte(15),
|
||||
},
|
||||
},
|
||||
TorrentFile: &torrentStructures.Torrent{
|
||||
Info: &torrentStructures.TorrentInfo{
|
||||
Files: []*torrentStructures.TorrentFile{
|
||||
&torrentStructures.TorrentFile{},
|
||||
&torrentStructures.TorrentFile{},
|
||||
&torrentStructures.TorrentFile{},
|
||||
&torrentStructures.TorrentFile{Path: []string{`/`}},
|
||||
&torrentStructures.TorrentFile{Path: []string{`/`}},
|
||||
&torrentStructures.TorrentFile{Path: []string{`/`}},
|
||||
&torrentStructures.TorrentFile{Path: []string{`/`}},
|
||||
&torrentStructures.TorrentFile{Path: []string{`/`}},
|
||||
&torrentStructures.TorrentFile{Path: []string{`/`}},
|
||||
&torrentStructures.TorrentFile{Path: []string{`/`}},
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -1266,121 +1329,125 @@ func TestTransferStructure_HandleState(t *testing.T) {
|
||||
{
|
||||
name: "005 started resume with parted downloaded files",
|
||||
newTransferStructure: &TransferStructure{
|
||||
Fastresume: &qBittorrentStructures.QBittorrentFastresume{},
|
||||
Fastresume: &qBittorrentStructures.QBittorrentFastresume{
|
||||
FilePriority: []int64{1, 0, 1, 1, 1, 6, 6},
|
||||
},
|
||||
ResumeItem: &utorrentStructs.ResumeItem{
|
||||
Started: 1,
|
||||
Prio: []byte{
|
||||
byte(0),
|
||||
byte(10),
|
||||
byte(2),
|
||||
byte(5),
|
||||
byte(8),
|
||||
byte(9),
|
||||
byte(15),
|
||||
},
|
||||
},
|
||||
TorrentFile: &torrentStructures.Torrent{
|
||||
Info: &torrentStructures.TorrentInfo{
|
||||
Files: []*torrentStructures.TorrentFile{
|
||||
&torrentStructures.TorrentFile{},
|
||||
&torrentStructures.TorrentFile{},
|
||||
&torrentStructures.TorrentFile{},
|
||||
&torrentStructures.TorrentFile{Path: []string{`/`}},
|
||||
&torrentStructures.TorrentFile{Path: []string{`/`}},
|
||||
&torrentStructures.TorrentFile{Path: []string{`/`}},
|
||||
&torrentStructures.TorrentFile{Path: []string{`/`}},
|
||||
&torrentStructures.TorrentFile{Path: []string{`/`}},
|
||||
&torrentStructures.TorrentFile{Path: []string{`/`}},
|
||||
&torrentStructures.TorrentFile{Path: []string{`/`}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: &TransferStructure{
|
||||
Fastresume: &qBittorrentStructures.QBittorrentFastresume{Paused: 1, AutoManaged: 0},
|
||||
Fastresume: &qBittorrentStructures.QBittorrentFastresume{
|
||||
Paused: 1,
|
||||
AutoManaged: 0,
|
||||
FilePriority: []int64{1, 0, 1, 1, 1, 6, 6},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "006 started resume with parted downloaded files",
|
||||
newTransferStructure: &TransferStructure{
|
||||
Fastresume: &qBittorrentStructures.QBittorrentFastresume{},
|
||||
Fastresume: &qBittorrentStructures.QBittorrentFastresume{
|
||||
FilePriority: []int64{1, 0, 1, 1, 1, 6, 6},
|
||||
},
|
||||
ResumeItem: &utorrentStructs.ResumeItem{
|
||||
Started: 1,
|
||||
Prio: []byte{
|
||||
byte(1),
|
||||
byte(128),
|
||||
byte(2),
|
||||
byte(5),
|
||||
byte(8),
|
||||
byte(9),
|
||||
byte(15),
|
||||
},
|
||||
},
|
||||
TorrentFile: &torrentStructures.Torrent{
|
||||
Info: &torrentStructures.TorrentInfo{
|
||||
Files: []*torrentStructures.TorrentFile{
|
||||
&torrentStructures.TorrentFile{},
|
||||
&torrentStructures.TorrentFile{},
|
||||
&torrentStructures.TorrentFile{},
|
||||
&torrentStructures.TorrentFile{Path: []string{`/`}},
|
||||
&torrentStructures.TorrentFile{Path: []string{`/`}},
|
||||
&torrentStructures.TorrentFile{Path: []string{`/`}},
|
||||
&torrentStructures.TorrentFile{Path: []string{`/`}},
|
||||
&torrentStructures.TorrentFile{Path: []string{`/`}},
|
||||
&torrentStructures.TorrentFile{Path: []string{`/`}},
|
||||
&torrentStructures.TorrentFile{Path: []string{`/`}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: &TransferStructure{
|
||||
Fastresume: &qBittorrentStructures.QBittorrentFastresume{Paused: 1, AutoManaged: 0},
|
||||
Fastresume: &qBittorrentStructures.QBittorrentFastresume{
|
||||
Paused: 1,
|
||||
AutoManaged: 0,
|
||||
FilePriority: []int64{1, 0, 1, 1, 1, 6, 6},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "007 started resume with full downloaded files",
|
||||
newTransferStructure: &TransferStructure{
|
||||
Fastresume: &qBittorrentStructures.QBittorrentFastresume{},
|
||||
Fastresume: &qBittorrentStructures.QBittorrentFastresume{
|
||||
FilePriority: []int64{1, 0, 1, 1, 1, 6, 6},
|
||||
},
|
||||
ResumeItem: &utorrentStructs.ResumeItem{
|
||||
Started: 1,
|
||||
Prio: []byte{
|
||||
byte(1),
|
||||
byte(128),
|
||||
byte(2),
|
||||
byte(5),
|
||||
byte(8),
|
||||
byte(9),
|
||||
byte(15),
|
||||
},
|
||||
},
|
||||
TorrentFile: &torrentStructures.Torrent{
|
||||
Info: &torrentStructures.TorrentInfo{
|
||||
Files: []*torrentStructures.TorrentFile{
|
||||
&torrentStructures.TorrentFile{},
|
||||
&torrentStructures.TorrentFile{},
|
||||
&torrentStructures.TorrentFile{},
|
||||
&torrentStructures.TorrentFile{Path: []string{`/`}},
|
||||
&torrentStructures.TorrentFile{Path: []string{`/`}},
|
||||
&torrentStructures.TorrentFile{Path: []string{`/`}},
|
||||
&torrentStructures.TorrentFile{Path: []string{`/`}},
|
||||
&torrentStructures.TorrentFile{Path: []string{`/`}},
|
||||
&torrentStructures.TorrentFile{Path: []string{`/`}},
|
||||
&torrentStructures.TorrentFile{Path: []string{`/`}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: &TransferStructure{
|
||||
Fastresume: &qBittorrentStructures.QBittorrentFastresume{Paused: 1, AutoManaged: 0},
|
||||
Fastresume: &qBittorrentStructures.QBittorrentFastresume{
|
||||
Paused: 1,
|
||||
AutoManaged: 0,
|
||||
FilePriority: []int64{1, 0, 1, 1, 1, 6, 6},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "008 started resume with parted downloaded files",
|
||||
newTransferStructure: &TransferStructure{
|
||||
Fastresume: &qBittorrentStructures.QBittorrentFastresume{},
|
||||
Fastresume: &qBittorrentStructures.QBittorrentFastresume{
|
||||
FilePriority: []int64{1, 0, 1, 1, 1, 6, 6},
|
||||
},
|
||||
ResumeItem: &utorrentStructs.ResumeItem{
|
||||
Started: 1,
|
||||
Prio: []byte{
|
||||
byte(1),
|
||||
byte(128),
|
||||
byte(2),
|
||||
byte(5),
|
||||
byte(8),
|
||||
byte(9),
|
||||
byte(15),
|
||||
},
|
||||
},
|
||||
TorrentFile: &torrentStructures.Torrent{
|
||||
Info: &torrentStructures.TorrentInfo{
|
||||
Files: []*torrentStructures.TorrentFile{
|
||||
&torrentStructures.TorrentFile{},
|
||||
&torrentStructures.TorrentFile{},
|
||||
&torrentStructures.TorrentFile{},
|
||||
&torrentStructures.TorrentFile{Path: []string{`/`}},
|
||||
&torrentStructures.TorrentFile{Path: []string{`/`}},
|
||||
&torrentStructures.TorrentFile{Path: []string{`/`}},
|
||||
&torrentStructures.TorrentFile{Path: []string{`/`}},
|
||||
&torrentStructures.TorrentFile{Path: []string{`/`}},
|
||||
&torrentStructures.TorrentFile{Path: []string{`/`}},
|
||||
&torrentStructures.TorrentFile{Path: []string{`/`}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: &TransferStructure{
|
||||
Fastresume: &qBittorrentStructures.QBittorrentFastresume{Paused: 1, AutoManaged: 0},
|
||||
Fastresume: &qBittorrentStructures.QBittorrentFastresume{
|
||||
Paused: 1,
|
||||
AutoManaged: 0,
|
||||
FilePriority: []int64{1, 0, 1, 1, 1, 6, 6},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -1389,15 +1456,6 @@ func TestTransferStructure_HandleState(t *testing.T) {
|
||||
Fastresume: &qBittorrentStructures.QBittorrentFastresume{},
|
||||
ResumeItem: &utorrentStructs.ResumeItem{
|
||||
Started: 0,
|
||||
Prio: []byte{
|
||||
byte(1),
|
||||
byte(128),
|
||||
byte(2),
|
||||
byte(5),
|
||||
byte(8),
|
||||
byte(9),
|
||||
byte(15),
|
||||
},
|
||||
},
|
||||
TorrentFile: &torrentStructures.Torrent{
|
||||
Info: &torrentStructures.TorrentInfo{},
|
||||
|
@ -27,3 +27,33 @@ func TestGetStrings(t *testing.T) {
|
||||
t.Fatalf("Unexpected error: opts isn't equal:\n Got: %#v\n Expect %#v\n", trackers, expect)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecodeTorrentFile(t *testing.T) {
|
||||
type PathJoinCase struct {
|
||||
name string
|
||||
mustFail bool
|
||||
path string
|
||||
}
|
||||
cases := []PathJoinCase{
|
||||
{
|
||||
name: "001 not existing file",
|
||||
path: "notexists.torrent",
|
||||
mustFail: true,
|
||||
},
|
||||
{
|
||||
name: "002 existing file",
|
||||
path: "../../test/data/testfileset.torrent",
|
||||
},
|
||||
}
|
||||
for _, testCase := range cases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
var decoded interface{}
|
||||
err := DecodeTorrentFile(testCase.path, &decoded)
|
||||
if err != nil && !testCase.mustFail {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
} else if err == nil && testCase.mustFail {
|
||||
t.Fatalf("Test must fail, but it doesn't")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -1,65 +1,63 @@
|
||||
package qBittorrentStructures
|
||||
|
||||
import "github.com/rumanzo/bt2qbt/pkg/torrentStructures"
|
||||
|
||||
// https://www.libtorrent.org/manual-ref.html
|
||||
|
||||
type QBittorrentFastresume struct {
|
||||
ActiveTime int64 `bencode:"active_time"` // integer. The number of seconds this torrent has been active. i.e. not paused.
|
||||
AddedTime int64 `bencode:"added_time"`
|
||||
Allocation string `bencode:"allocation"` // The allocation mode for the storage. Can be either allocate or sparse.
|
||||
ApplyIpFilter int64 `bencode:"apply_ip_filter"` // integer. 1 if the torrent_flags::apply_ip_filter is set.
|
||||
AutoManaged int64 `bencode:"auto_managed"` // integer. 1 if the torrent is auto managed, otherwise 0.
|
||||
BannedPeers []byte `bencode:"banned_peers"` // string. This string has the same format as peers but instead represent IPv4 peers that we have banned.
|
||||
BannedPeers6 []byte `bencode:"banned_peers6"` // string. This string has the same format as peers6 but instead represent IPv6 peers that we have banned.
|
||||
CompletedTime int64 `bencode:"completed_time"`
|
||||
DisableDht int64 `bencode:"disable_dht"` // integer. 1 if the torrent_flags::disable_dht is set.
|
||||
DisableLsd int64 `bencode:"disable_lsd"` // integer. 1 if the torrent_flags::disable_lsd is set.
|
||||
DisablePex int64 `bencode:"disable_pex"` // integer. 1 if the torrent_flags::disable_pex is set.
|
||||
DownloadRateLimit int64 `bencode:"download_rate_limit"` // integer. The download rate limit for this torrent in case one is set, in bytes per second.
|
||||
FileFormat string `bencode:"file-format"` // string: "libtorrent resume file"
|
||||
FilePriority []int64 `bencode:"file_priority"` // list of integers. One entry per file in the torrent. Each entry is the priority of the file with the same index.
|
||||
FileVersion int64 `bencode:"file-version"`
|
||||
FinishedTime int64 `bencode:"finished_time"`
|
||||
HttpSeeds []string `bencode:"httpseeds"` // list of strings. List of HTTP seed URLs used by this torrent. The URLs are expected to be properly encoded and not contain any illegal url characters.
|
||||
Info *torrentStructures.TorrentInfo `bencode:"info,omitempty"` // If this field is present, it should be the info-dictionary of the torrent this resume data is for. Its SHA-1 hash must match the one in the info-hash field. When present, the torrent is loaded from here, meaning the torrent can be added purely from resume data (no need to load the .torrent file separately). This may have performance advantages.
|
||||
InfoHash string `bencode:"info-hash"` // string, the info hash of the torrent this data is saved for. This is a 20 byte SHA-1 hash of the info section of the torrent if this is a v1 or v1+v2-hybrid torrent.
|
||||
InfoHash2 string `bencode:"info-hash2"` // string, the v2 info hash of the torrent this data is saved. for, in case it is a v2 or v1+v2-hybrid torrent. This is a 32 byte SHA-256 hash of the info section of the torrent.
|
||||
LastDownload int64 `bencode:"last_download"` // integer. The number of seconds since epoch when we last downloaded payload from a peer on this torrent.
|
||||
LastSeenComplete int64 `bencode:"last_seen_complete"`
|
||||
LastUpload int64 `bencode:"last_upload"` // integer. The number of seconds since epoch when we last uploaded payload to a peer on this torrent.
|
||||
LibTorrentVersion string `bencode:"libtorrent-version"`
|
||||
MappedFiles []string `bencode:"mapped_files,omitempty"` // list of strings. If any file in the torrent has been renamed, this entry contains a list of all the filenames. In the same order as in the torrent file.
|
||||
MaxConnections int64 `bencode:"max_connections"` // integer. The max number of peer connections this torrent may have, if a limit is set.
|
||||
MaxUploads int64 `bencode:"max_uploads"` // integer. The max number of unchoked peers this torrent may have, if a limit is set.
|
||||
NumComplete int64 `bencode:"num_complete"`
|
||||
NumDownloaded int64 `bencode:"num_downloaded"`
|
||||
NumIncomplete int64 `bencode:"num_incomplete"`
|
||||
Paused int64 `bencode:"paused"` // integer. 1 if the torrent is paused, 0 otherwise.
|
||||
Peers int64 `bencode:"peers"` // string. This string contains IPv4 and port pairs of peers we were connected to last session. The endpoints are in compact representation. 4 bytes IPv4 address followed by 2 bytes port. Hence, the length of this string should be divisible by 6.
|
||||
Peers6 int64 `bencode:"peers6"` // string. This string contains IPv6 and port pairs of peers we were connected to last session. The endpoints are in compact representation. 16 bytes IPv6 address followed by 2 bytes port. The length of this string should be divisible by 18.
|
||||
Pieces []byte `bencode:"pieces"` // A string with piece flags, one character per piece. Bit 1 means we have that piece. Bit 2 means we have verified that this piece is correct. This only applies when the torrent is in seed_mode.
|
||||
QBtCategory string `bencode:"qBt-category"`
|
||||
QBtContentLayout string `bencode:"qBt-contentLayout"` // Original, Subfolder, NoSubfolder
|
||||
QBtFirstLastPiecePriority string `bencode:"qBt-firstLastPiecePriority"`
|
||||
QbtName string `bencode:"qBt-name"`
|
||||
QbtRatioLimit int64 `bencode:"qBt-ratioLimit"`
|
||||
QbtSavePath string `bencode:"qBt-savePath"`
|
||||
QbtSeedStatus int64 `bencode:"qBt-seedStatus"`
|
||||
QbtSeedingTimeLimit int64 `bencode:"qBt-seedingTimeLimit"`
|
||||
QbtTags []string `bencode:"qBt-tags"`
|
||||
SavePath string `bencode:"save_path"` // string. The save path where this torrent was saved. This is especially useful when moving torrents with move_storage() since this will be updated.
|
||||
SeedMode int64 `bencode:"seed_mode"` // integer. 1 if the torrent is in seed mode, 0 otherwise.
|
||||
SeedingTime int64 `bencode:"seeding_time"` // integer. The number of seconds this torrent has been active and seeding.
|
||||
SequentialDownload int64 `bencode:"sequential_download"` // integer. 1 if the torrent is in sequential download mode, 0 otherwise.
|
||||
ShareMode int64 `bencode:"share_mode"` // integer. 1 if the torrent_flags::share_mode is set.
|
||||
StopWhenReady int64 `bencode:"stop_when_ready"` // integer. 1 if the torrent_flags::stop_when_ready is set.
|
||||
SuperSeeding int64 `bencode:"super_seeding"` // integer. 1 if the torrent_flags::super_seeding is set.
|
||||
TotalDownloaded int64 `bencode:"total_downloaded"` // integer. The number of bytes that have been downloaded in total for this torrent.
|
||||
TotalUploaded int64 `bencode:"total_uploaded"` // integer. The number of bytes that have been uploaded in total for this torrent.
|
||||
Trackers [][]string `bencode:"trackers"` // list of lists of strings. The top level list lists all tracker tiers. Each second level list is one tier of trackers.
|
||||
Unfinished *[]interface{} `bencode:"unfinished,omitempty"`
|
||||
UploadMode int64 `bencode:"upload_mode"` // integer. 1 if the torrent_flags::upload_mode is set.
|
||||
UploadRateLimit int64 `bencode:"upload_rate_limit"` // integer. In case this torrent has a per-torrent upload rate limit, this is that limit. In bytes per second.
|
||||
UrlList int64 `bencode:"url-list"` // list of strings. List of url-seed URLs used by this torrent. The URLs are expected to be properly encoded and not contain any illegal url characters.
|
||||
ActiveTime int64 `bencode:"active_time"` // integer. The number of seconds this torrent has been active. i.e. not paused.
|
||||
AddedTime int64 `bencode:"added_time"`
|
||||
Allocation string `bencode:"allocation"` // The allocation mode for the storage. Can be either allocate or sparse.
|
||||
ApplyIpFilter int64 `bencode:"apply_ip_filter"` // integer. 1 if the torrent_flags::apply_ip_filter is set.
|
||||
AutoManaged int64 `bencode:"auto_managed"` // integer. 1 if the torrent is auto managed, otherwise 0.
|
||||
BannedPeers []byte `bencode:"banned_peers"` // string. This string has the same format as peers but instead represent IPv4 peers that we have banned.
|
||||
BannedPeers6 []byte `bencode:"banned_peers6"` // string. This string has the same format as peers6 but instead represent IPv6 peers that we have banned.
|
||||
CompletedTime int64 `bencode:"completed_time"`
|
||||
DisableDht int64 `bencode:"disable_dht"` // integer. 1 if the torrent_flags::disable_dht is set.
|
||||
DisableLsd int64 `bencode:"disable_lsd"` // integer. 1 if the torrent_flags::disable_lsd is set.
|
||||
DisablePex int64 `bencode:"disable_pex"` // integer. 1 if the torrent_flags::disable_pex is set.
|
||||
DownloadRateLimit int64 `bencode:"download_rate_limit"` // integer. The download rate limit for this torrent in case one is set, in bytes per second.
|
||||
FileFormat string `bencode:"file-format"` // string: "libtorrent resume file"
|
||||
FilePriority []int64 `bencode:"file_priority"` // list of integers. One entry per file in the torrent. Each entry is the priority of the file with the same index.
|
||||
FileVersion int64 `bencode:"file-version"`
|
||||
FinishedTime int64 `bencode:"finished_time"`
|
||||
HttpSeeds []string `bencode:"httpseeds"` // list of strings. List of HTTP seed URLs used by this torrent. The URLs are expected to be properly encoded and not contain any illegal url characters.
|
||||
Info interface{} `bencode:"info,omitempty"` // If this field is present, it should be the info-dictionary of the torrent this resume data is for. Its SHA-1 hash must match the one in the info-hash field. When present, the torrent is loaded from here, meaning the torrent can be added purely from resume data (no need to load the .torrent file separately). This may have performance advantages.
|
||||
InfoHash string `bencode:"info-hash"` // string, the info hash of the torrent this data is saved for. This is a 20 byte SHA-1 hash of the info section of the torrent if this is a v1 or v1+v2-hybrid torrent.
|
||||
InfoHash2 string `bencode:"info-hash2"` // string, the v2 info hash of the torrent this data is saved. for, in case it is a v2 or v1+v2-hybrid torrent. This is a 32 byte SHA-256 hash of the info section of the torrent.
|
||||
LastDownload int64 `bencode:"last_download"` // integer. The number of seconds since epoch when we last downloaded payload from a peer on this torrent.
|
||||
LastSeenComplete int64 `bencode:"last_seen_complete"`
|
||||
LastUpload int64 `bencode:"last_upload"` // integer. The number of seconds since epoch when we last uploaded payload to a peer on this torrent.
|
||||
LibTorrentVersion string `bencode:"libtorrent-version"`
|
||||
MappedFiles []string `bencode:"mapped_files,omitempty"` // list of strings. If any file in the torrent has been renamed, this entry contains a list of all the filenames. In the same order as in the torrent file.
|
||||
MaxConnections int64 `bencode:"max_connections"` // integer. The max number of peer connections this torrent may have, if a limit is set.
|
||||
MaxUploads int64 `bencode:"max_uploads"` // integer. The max number of unchoked peers this torrent may have, if a limit is set.
|
||||
NumComplete int64 `bencode:"num_complete"`
|
||||
NumDownloaded int64 `bencode:"num_downloaded"`
|
||||
NumIncomplete int64 `bencode:"num_incomplete"`
|
||||
Paused int64 `bencode:"paused"` // integer. 1 if the torrent is paused, 0 otherwise.
|
||||
Peers string `bencode:"peers"` // string. This string contains IPv4 and port pairs of peers we were connected to last session. The endpoints are in compact representation. 4 bytes IPv4 address followed by 2 bytes port. Hence, the length of this string should be divisible by 6.
|
||||
Peers6 string `bencode:"peers6"` // string. This string contains IPv6 and port pairs of peers we were connected to last session. The endpoints are in compact representation. 16 bytes IPv6 address followed by 2 bytes port. The length of this string should be divisible by 18.
|
||||
Pieces []byte `bencode:"pieces"` // A string with piece flags, one character per piece. Bit 1 means we have that piece. Bit 2 means we have verified that this piece is correct. This only applies when the torrent is in seed_mode.
|
||||
QBtCategory string `bencode:"qBt-category"`
|
||||
QBtContentLayout string `bencode:"qBt-contentLayout"` // Original, Subfolder, NoSubfolder
|
||||
QBtFirstLastPiecePriority int64 `bencode:"qBt-firstLastPiecePriority"`
|
||||
QbtName string `bencode:"qBt-name"`
|
||||
QbtRatioLimit int64 `bencode:"qBt-ratioLimit"`
|
||||
QbtSavePath string `bencode:"qBt-savePath"`
|
||||
QbtSeedStatus int64 `bencode:"qBt-seedStatus"`
|
||||
QbtSeedingTimeLimit int64 `bencode:"qBt-seedingTimeLimit"`
|
||||
QbtTags []string `bencode:"qBt-tags"`
|
||||
SavePath string `bencode:"save_path"` // string. The save path where this torrent was saved. This is especially useful when moving torrents with move_storage() since this will be updated.
|
||||
SeedMode int64 `bencode:"seed_mode"` // integer. 1 if the torrent is in seed mode, 0 otherwise.
|
||||
SeedingTime int64 `bencode:"seeding_time"` // integer. The number of seconds this torrent has been active and seeding.
|
||||
SequentialDownload int64 `bencode:"sequential_download"` // integer. 1 if the torrent is in sequential download mode, 0 otherwise.
|
||||
ShareMode int64 `bencode:"share_mode"` // integer. 1 if the torrent_flags::share_mode is set.
|
||||
StopWhenReady int64 `bencode:"stop_when_ready"` // integer. 1 if the torrent_flags::stop_when_ready is set.
|
||||
SuperSeeding int64 `bencode:"super_seeding"` // integer. 1 if the torrent_flags::super_seeding is set.
|
||||
TotalDownloaded int64 `bencode:"total_downloaded"` // integer. The number of bytes that have been downloaded in total for this torrent.
|
||||
TotalUploaded int64 `bencode:"total_uploaded"` // integer. The number of bytes that have been uploaded in total for this torrent.
|
||||
Trackers [][]string `bencode:"trackers"` // list of lists of strings. The top level list lists all tracker tiers. Each second level list is one tier of trackers.
|
||||
Unfinished *[]interface{} `bencode:"unfinished,omitempty"`
|
||||
UploadMode int64 `bencode:"upload_mode"` // integer. 1 if the torrent_flags::upload_mode is set.
|
||||
UploadRateLimit int64 `bencode:"upload_rate_limit"` // integer. In case this torrent has a per-torrent upload rate limit, this is that limit. In bytes per second.
|
||||
UrlList []string `bencode:"url-list"` // list of strings. List of url-seed URLs used by this torrent. The URLs are expected to be properly encoded and not contain any illegal url characters.
|
||||
}
|
||||
|
56
pkg/qBittorrentStructures/qBittorrent_test.go
Normal file
56
pkg/qBittorrentStructures/qBittorrent_test.go
Normal file
@ -0,0 +1,56 @@
|
||||
package qBittorrentStructures
|
||||
|
||||
import (
|
||||
"github.com/rumanzo/bt2qbt/pkg/helpers"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDecodeFastresumeFile(t *testing.T) {
|
||||
type PathJoinCase struct {
|
||||
name string
|
||||
mustFail bool
|
||||
path string
|
||||
}
|
||||
cases := []PathJoinCase{
|
||||
{
|
||||
name: "001 not existing file",
|
||||
path: "notexists.fastresume",
|
||||
mustFail: true,
|
||||
},
|
||||
{
|
||||
name: "002 testdir hybryd",
|
||||
path: "../../test/data/testdir_hybrid.fastresume",
|
||||
},
|
||||
{
|
||||
name: "003 testdir v1",
|
||||
path: "../../test/data/testdir_v1.fastresume",
|
||||
},
|
||||
{
|
||||
name: "004 testdir v2",
|
||||
path: "../../test/data/testdir_v2.fastresume",
|
||||
},
|
||||
{
|
||||
name: "005 single hybryd",
|
||||
path: "../../test/data/testfile1_single_hybrid.fastresume",
|
||||
},
|
||||
{
|
||||
name: "006 single v1",
|
||||
path: "../../test/data/testfile1_single_v1.fastresume",
|
||||
},
|
||||
{
|
||||
name: "007 single v2",
|
||||
path: "../../test/data/testfile1_single_v2.fastresume",
|
||||
},
|
||||
}
|
||||
for _, testCase := range cases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
var decoded QBittorrentFastresume
|
||||
err := helpers.DecodeTorrentFile(testCase.path, &decoded)
|
||||
if err != nil && !testCase.mustFail {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
} else if err == nil && testCase.mustFail {
|
||||
t.Fatalf("Test must fail, but it doesn't")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
85
pkg/torrentStructures/functions.go
Normal file
85
pkg/torrentStructures/functions.go
Normal file
@ -0,0 +1,85 @@
|
||||
package torrentStructures
|
||||
|
||||
import (
|
||||
"github.com/rumanzo/bt2qbt/pkg/fileHelpers"
|
||||
"sort"
|
||||
)
|
||||
|
||||
func (t *Torrent) IsV2OrHybryd() bool {
|
||||
if t.Info.FileTree != nil {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// GetFileListWB function that return struct with filelists with bytes from torrent file
|
||||
func (t *Torrent) GetFileListWB() []FilepathLength {
|
||||
if t.FilePathLength == nil {
|
||||
if t.IsV2OrHybryd() { // torrents with v2 or hybrid scheme
|
||||
result := getFileListV2(t.Info.FileTree)
|
||||
t.FilePathLength = &result
|
||||
return *t.FilePathLength
|
||||
} else { // torrent v1 with FileTree
|
||||
result := getFileListV1(t)
|
||||
t.FilePathLength = &result
|
||||
return *t.FilePathLength
|
||||
}
|
||||
} else {
|
||||
return *t.FilePathLength
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Torrent) GetFileList() []string {
|
||||
if t.FilePathLength == nil {
|
||||
t.GetFileListWB()
|
||||
}
|
||||
if t.FilePaths == nil {
|
||||
t.FilePaths = &[]string{}
|
||||
for _, fb := range *t.FilePathLength {
|
||||
*t.FilePaths = append(*t.FilePaths, fb.Path)
|
||||
}
|
||||
}
|
||||
return *t.FilePaths
|
||||
}
|
||||
|
||||
func getFileListV1(t *Torrent) []FilepathLength {
|
||||
var files []FilepathLength
|
||||
for _, file := range t.Info.Files {
|
||||
if file.PathUTF8 != nil {
|
||||
files = append(files, FilepathLength{
|
||||
Path: fileHelpers.Join(file.PathUTF8, `/`),
|
||||
Length: file.Length,
|
||||
})
|
||||
} else {
|
||||
files = append(files, FilepathLength{
|
||||
Path: fileHelpers.Join(file.Path, `/`),
|
||||
Length: file.Length,
|
||||
})
|
||||
}
|
||||
}
|
||||
return files
|
||||
}
|
||||
|
||||
func getFileListV2(f interface{}) []FilepathLength {
|
||||
nfiles := []FilepathLength{}
|
||||
|
||||
// sort map previously
|
||||
keys := make([]string, 0, len(f.(map[string]interface{})))
|
||||
for k := range f.(map[string]interface{}) {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
for _, k := range keys {
|
||||
v := f.(map[string]interface{})[k]
|
||||
if len(k) == 0 { // it's means that next will be structure with length and piece root
|
||||
nfiles = append(nfiles, FilepathLength{Path: "", Length: v.(map[string]interface{})["length"].(int64)})
|
||||
return nfiles
|
||||
}
|
||||
s := getFileListV2(v)
|
||||
for _, fpl := range s {
|
||||
nfiles = append(nfiles, FilepathLength{Path: fileHelpers.Join(append([]string{k}, fpl.Path), `/`), Length: fpl.Length})
|
||||
}
|
||||
}
|
||||
return nfiles
|
||||
}
|
227
pkg/torrentStructures/functions_test.go
Normal file
227
pkg/torrentStructures/functions_test.go
Normal file
@ -0,0 +1,227 @@
|
||||
package torrentStructures
|
||||
|
||||
import (
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/r3labs/diff/v2"
|
||||
"github.com/rumanzo/bt2qbt/pkg/helpers"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDecodeRealTorrents(t *testing.T) {
|
||||
type PathJoinCase struct {
|
||||
name string
|
||||
mustFail bool
|
||||
path string
|
||||
}
|
||||
cases := []PathJoinCase{
|
||||
{
|
||||
name: "001 not existing file",
|
||||
path: "notexists.torrent",
|
||||
mustFail: true,
|
||||
},
|
||||
{
|
||||
name: "002 existing file",
|
||||
path: "../../test/data/testfileset.torrent",
|
||||
},
|
||||
{
|
||||
name: "003 testdir hybryd",
|
||||
path: "../../test/data/testdir_hybrid.torrent",
|
||||
},
|
||||
{
|
||||
name: "004 testdir v1",
|
||||
path: "../../test/data/testdir_v1.torrent",
|
||||
},
|
||||
{
|
||||
name: "005 testdir v2",
|
||||
path: "../../test/data/testdir_v2.torrent",
|
||||
},
|
||||
{
|
||||
name: "006 single hybryd",
|
||||
path: "../../test/data/testfile1_single_hybrid.torrent",
|
||||
},
|
||||
{
|
||||
name: "007 single v1",
|
||||
path: "../../test/data/testfile1_single_v1.torrent",
|
||||
},
|
||||
{
|
||||
name: "008 single v2",
|
||||
path: "../../test/data/testfile1_single_v2.torrent",
|
||||
},
|
||||
}
|
||||
for _, testCase := range cases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
var torrent Torrent
|
||||
err := helpers.DecodeTorrentFile(testCase.path, &torrent)
|
||||
if err != nil && !testCase.mustFail {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
} else if err == nil && testCase.mustFail {
|
||||
t.Fatalf("Test must fail, but it doesn't")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTorrent_GetFileList(t *testing.T) {
|
||||
type PathJoinCase struct {
|
||||
name string
|
||||
path string
|
||||
expected []string
|
||||
mustFail bool
|
||||
}
|
||||
cases := []PathJoinCase{
|
||||
{
|
||||
name: "001 testdir v2 mustfail",
|
||||
path: "../../test/data/testdir_v2.torrent",
|
||||
mustFail: true,
|
||||
expected: []string{},
|
||||
},
|
||||
{
|
||||
name: "002 testdir v2",
|
||||
path: "../../test/data/testdir_v2.torrent",
|
||||
expected: []string{
|
||||
"dir1/testfile1.txt",
|
||||
"dir2/testfile1.txt",
|
||||
"dir2/testfile2.txt",
|
||||
"dir3/testfile1.txt",
|
||||
"dir3/testfile2.txt",
|
||||
"dir3/testfile3.txt",
|
||||
"testfile1.txt",
|
||||
"testfile2.txt",
|
||||
"testfile3.txt",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "003 testdir v1",
|
||||
path: "../../test/data/testdir_v1.torrent",
|
||||
expected: []string{
|
||||
"testfile1.txt",
|
||||
"testfile2.txt",
|
||||
"testfile3.txt",
|
||||
"dir1/testfile1.txt",
|
||||
"dir2/testfile1.txt",
|
||||
"dir2/testfile2.txt",
|
||||
"dir3/testfile1.txt",
|
||||
"dir3/testfile2.txt",
|
||||
"dir3/testfile3.txt",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "004 testdir hybrid",
|
||||
path: "../../test/data/testdir_hybrid.torrent",
|
||||
expected: []string{
|
||||
"dir1/testfile1.txt",
|
||||
"dir2/testfile1.txt",
|
||||
"dir2/testfile2.txt",
|
||||
"dir3/testfile1.txt",
|
||||
"dir3/testfile2.txt",
|
||||
"dir3/testfile3.txt",
|
||||
"testfile1.txt",
|
||||
"testfile2.txt",
|
||||
"testfile3.txt",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, testCase := range cases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
var torrent Torrent
|
||||
err := helpers.DecodeTorrentFile(testCase.path, &torrent)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error with decoding torrent file: %v", err)
|
||||
}
|
||||
list := torrent.GetFileList()
|
||||
equal := reflect.DeepEqual(list, testCase.expected)
|
||||
if !equal && !testCase.mustFail {
|
||||
changes, err := diff.Diff(list, testCase.expected, diff.DiscardComplexOrigin())
|
||||
if err != nil {
|
||||
t.Error(err.Error())
|
||||
}
|
||||
t.Fatalf("Unexpected error: opts isn't equal:\n Got: %#v\n Expect %#v\n Diff: %v\n", list, testCase.expected, spew.Sdump(changes))
|
||||
} else if equal && testCase.mustFail {
|
||||
t.Fatalf("Unexpected error: structures are equal, but they shouldn't\n Got: %v\n", spew.Sdump(list))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTorrent_GetFileListWB(t *testing.T) {
|
||||
type PathJoinCase struct {
|
||||
name string
|
||||
path string
|
||||
expected []FilepathLength
|
||||
mustFail bool
|
||||
}
|
||||
cases := []PathJoinCase{
|
||||
{
|
||||
name: "001 testdir v2 mustfail",
|
||||
path: "../../test/data/testdir_v2.torrent",
|
||||
mustFail: true,
|
||||
expected: []FilepathLength{},
|
||||
},
|
||||
{
|
||||
name: "001 testdir v2",
|
||||
path: "../../test/data/testdir_v2.torrent",
|
||||
expected: []FilepathLength{
|
||||
FilepathLength{Path: "dir1/testfile1.txt", Length: 33},
|
||||
FilepathLength{Path: "dir2/testfile1.txt", Length: 33},
|
||||
FilepathLength{Path: "dir2/testfile2.txt", Length: 33},
|
||||
FilepathLength{Path: "dir3/testfile1.txt", Length: 33},
|
||||
FilepathLength{Path: "dir3/testfile2.txt", Length: 33},
|
||||
FilepathLength{Path: "dir3/testfile3.txt", Length: 33},
|
||||
FilepathLength{Path: "testfile1.txt", Length: 33},
|
||||
FilepathLength{Path: "testfile2.txt", Length: 33},
|
||||
FilepathLength{Path: "testfile3.txt", Length: 33},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "003 testdir v1",
|
||||
path: "../../test/data/testdir_v1.torrent",
|
||||
expected: []FilepathLength{
|
||||
FilepathLength{Path: "testfile1.txt", Length: 33},
|
||||
FilepathLength{Path: "testfile2.txt", Length: 33},
|
||||
FilepathLength{Path: "testfile3.txt", Length: 33},
|
||||
FilepathLength{Path: "dir1/testfile1.txt", Length: 33},
|
||||
FilepathLength{Path: "dir2/testfile1.txt", Length: 33},
|
||||
FilepathLength{Path: "dir2/testfile2.txt", Length: 33},
|
||||
FilepathLength{Path: "dir3/testfile1.txt", Length: 33},
|
||||
FilepathLength{Path: "dir3/testfile2.txt", Length: 33},
|
||||
FilepathLength{Path: "dir3/testfile3.txt", Length: 33},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "004 testdir hybrid",
|
||||
path: "../../test/data/testdir_hybrid.torrent",
|
||||
expected: []FilepathLength{
|
||||
FilepathLength{Path: "dir1/testfile1.txt", Length: 33},
|
||||
FilepathLength{Path: "dir2/testfile1.txt", Length: 33},
|
||||
FilepathLength{Path: "dir2/testfile2.txt", Length: 33},
|
||||
FilepathLength{Path: "dir3/testfile1.txt", Length: 33},
|
||||
FilepathLength{Path: "dir3/testfile2.txt", Length: 33},
|
||||
FilepathLength{Path: "dir3/testfile3.txt", Length: 33},
|
||||
FilepathLength{Path: "testfile1.txt", Length: 33},
|
||||
FilepathLength{Path: "testfile2.txt", Length: 33},
|
||||
FilepathLength{Path: "testfile3.txt", Length: 33},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, testCase := range cases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
var torrent Torrent
|
||||
err := helpers.DecodeTorrentFile(testCase.path, &torrent)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error with decoding torrent file: %v", err)
|
||||
}
|
||||
list := torrent.GetFileListWB()
|
||||
equal := reflect.DeepEqual(list, testCase.expected)
|
||||
if !equal && !testCase.mustFail {
|
||||
changes, err := diff.Diff(list, testCase.expected, diff.DiscardComplexOrigin())
|
||||
if err != nil {
|
||||
t.Error(err.Error())
|
||||
}
|
||||
t.Fatalf("Unexpected error: opts isn't equal:\n Got: %#v\n Expect %#v\n Diff: %v\n", list, testCase.expected, spew.Sdump(changes))
|
||||
} else if equal && testCase.mustFail {
|
||||
t.Fatalf("Unexpected error: structures are equal, but they shouldn't\n Got: %v\n", spew.Sdump(list))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -1,35 +1,32 @@
|
||||
package torrentStructures
|
||||
|
||||
type Torrent struct {
|
||||
Announce string `bencode:"announce"`
|
||||
Comment string `bencode:"comment"`
|
||||
CreatedBy string `bencode:"created by"`
|
||||
CreationDate int64 `bencode:"creation date"`
|
||||
Info *TorrentInfo `bencode:"info"`
|
||||
Publisher string `bencode:"publisher,omitempty"`
|
||||
PublisherUrl string `bencode:"publisher-url,omitempty"`
|
||||
PieceLayers *map[byte][]byte `bencode:"piece layers"`
|
||||
Announce string `bencode:"announce"`
|
||||
Comment string `bencode:"comment"`
|
||||
CreatedBy string `bencode:"created by"`
|
||||
CreationDate int64 `bencode:"creation date"`
|
||||
Info *TorrentInfo `bencode:"info"`
|
||||
Publisher string `bencode:"publisher,omitempty"`
|
||||
PublisherUrl string `bencode:"publisher-url,omitempty"`
|
||||
PieceLayers *map[string]interface{} `bencode:"piece layers"`
|
||||
FilePathLength *[]FilepathLength `bencode:"-"` // service field
|
||||
FilePaths *[]string `bencode:"-"` // service field
|
||||
}
|
||||
|
||||
type TorrentInfo struct {
|
||||
FileDuration []int64 `bencode:"file-duration,omitempty"`
|
||||
FileMedia []int64 `bencode:"file-media,omitempty"`
|
||||
Files []*TorrentFile `bencode:"files,omitempty"`
|
||||
FileTree map[string]*[]TorrentFileTree `bencode:"file tree,omitempty"`
|
||||
Length int64 `bencode:"length,omitempty"`
|
||||
MetaVersion int64 `bencode:"meta version,omitempty"`
|
||||
Md5sum string `bencode:"md5sum,omitempty"`
|
||||
Name string `bencode:"name,omitempty"`
|
||||
NameUTF8 string `bencode:"name.utf-8,omitempty"`
|
||||
PieceLength int64 `bencode:"piece length,omitempty"`
|
||||
Pieces []byte `bencode:"pieces,omitempty"`
|
||||
Private uint8 `bencode:"private,omitempty"`
|
||||
Profiles []*TorrentProfile `bencode:"profiles,omitempty"`
|
||||
}
|
||||
|
||||
type TorrentFileTree struct {
|
||||
Length int64 `bencode:"length,omitempty"`
|
||||
PiecesRoot []byte `bencode:"pieces root"`
|
||||
FileDuration []int64 `bencode:"file-duration,omitempty"`
|
||||
FileMedia []int64 `bencode:"file-media,omitempty"`
|
||||
Files []*TorrentFile `bencode:"files,omitempty"`
|
||||
FileTree map[string]interface{} `bencode:"file tree,omitempty"`
|
||||
Length int64 `bencode:"length,omitempty"`
|
||||
MetaVersion int64 `bencode:"meta version,omitempty"`
|
||||
Md5sum string `bencode:"md5sum,omitempty"`
|
||||
Name string `bencode:"name,omitempty"`
|
||||
NameUTF8 string `bencode:"name.utf-8,omitempty"`
|
||||
PieceLength int64 `bencode:"piece length,omitempty"`
|
||||
Pieces []byte `bencode:"pieces,omitempty"`
|
||||
Private uint8 `bencode:"private,omitempty"`
|
||||
Profiles []*TorrentProfile `bencode:"profiles,omitempty"`
|
||||
}
|
||||
|
||||
type TorrentFile struct {
|
||||
@ -45,3 +42,8 @@ type TorrentProfile struct {
|
||||
Vcodec []byte `bencode:"vcodec,omitempty"`
|
||||
Width int64 `bencode:"width,omitempty"`
|
||||
}
|
||||
|
||||
type FilepathLength struct {
|
||||
Path string
|
||||
Length int64
|
||||
}
|
||||
|
BIN
test/data/testdir_hybrid.fastresume
Normal file
BIN
test/data/testdir_hybrid.fastresume
Normal file
Binary file not shown.
BIN
test/data/testdir_hybrid.torrent
Normal file
BIN
test/data/testdir_hybrid.torrent
Normal file
Binary file not shown.
BIN
test/data/testdir_v1.fastresume
Normal file
BIN
test/data/testdir_v1.fastresume
Normal file
Binary file not shown.
1
test/data/testdir_v1.torrent
Normal file
1
test/data/testdir_v1.torrent
Normal file
@ -0,0 +1 @@
|
||||
d10:created by18:qBittorrent v4.4.213:creation datei1650146608e4:infod5:filesld6:lengthi33e4:pathl13:testfile1.txteed6:lengthi33e4:pathl13:testfile2.txteed6:lengthi33e4:pathl13:testfile3.txteed6:lengthi33e4:pathl4:dir113:testfile1.txteed6:lengthi33e4:pathl4:dir213:testfile1.txteed6:lengthi33e4:pathl4:dir213:testfile2.txteed6:lengthi33e4:pathl4:dir313:testfile1.txteed6:lengthi33e4:pathl4:dir313:testfile2.txteed6:lengthi33e4:pathl4:dir313:testfile3.txteee4:name7:testdir12:piece lengthi16384e6:pieces20:ã9Ép¶ç õAOͯw»såÕ64ee
|
BIN
test/data/testdir_v2.fastresume
Normal file
BIN
test/data/testdir_v2.fastresume
Normal file
Binary file not shown.
10
test/data/testdir_v2.torrent
Normal file
10
test/data/testdir_v2.torrent
Normal file
@ -0,0 +1,10 @@
|
||||
d10:created by18:qBittorrent v4.4.213:creation datei1650146556e4:infod9:file treed4:dir1d13:testfile1.txtd0:d6:lengthi33e11:pieces root32:à1
|
||||
x²ü„Ã3ÉGÏÐϾ¬0þ(2üËÁ,/Öneee4:dir2d13:testfile1.txtd0:d6:lengthi33e11:pieces root32:à1
|
||||
x²ü„Ã3ÉGÏÐϾ¬0þ(2üËÁ,/Önee13:testfile2.txtd0:d6:lengthi33e11:pieces root32:à1
|
||||
x²ü„Ã3ÉGÏÐϾ¬0þ(2üËÁ,/Öneee4:dir3d13:testfile1.txtd0:d6:lengthi33e11:pieces root32:à1
|
||||
x²ü„Ã3ÉGÏÐϾ¬0þ(2üËÁ,/Önee13:testfile2.txtd0:d6:lengthi33e11:pieces root32:à1
|
||||
x²ü„Ã3ÉGÏÐϾ¬0þ(2üËÁ,/Önee13:testfile3.txtd0:d6:lengthi33e11:pieces root32:à1
|
||||
x²ü„Ã3ÉGÏÐϾ¬0þ(2üËÁ,/Öneee13:testfile1.txtd0:d6:lengthi33e11:pieces root32:à1
|
||||
x²ü„Ã3ÉGÏÐϾ¬0þ(2üËÁ,/Önee13:testfile2.txtd0:d6:lengthi33e11:pieces root32:à1
|
||||
x²ü„Ã3ÉGÏÐϾ¬0þ(2üËÁ,/Önee13:testfile3.txtd0:d6:lengthi33e11:pieces root32:à1
|
||||
x²ü„Ã3ÉGÏÐϾ¬0þ(2üËÁ,/Öneee12:meta versioni2e4:name7:testdir12:piece lengthi16384ee12:piece layersdee
|
BIN
test/data/testfile1_single_hybrid.fastresume
Normal file
BIN
test/data/testfile1_single_hybrid.fastresume
Normal file
Binary file not shown.
BIN
test/data/testfile1_single_hybrid.torrent
Normal file
BIN
test/data/testfile1_single_hybrid.torrent
Normal file
Binary file not shown.
BIN
test/data/testfile1_single_v1.fastresume
Normal file
BIN
test/data/testfile1_single_v1.fastresume
Normal file
Binary file not shown.
BIN
test/data/testfile1_single_v1.torrent
Normal file
BIN
test/data/testfile1_single_v1.torrent
Normal file
Binary file not shown.
BIN
test/data/testfile1_single_v2.fastresume
Normal file
BIN
test/data/testfile1_single_v2.fastresume
Normal file
Binary file not shown.
2
test/data/testfile1_single_v2.torrent
Normal file
2
test/data/testfile1_single_v2.torrent
Normal file
@ -0,0 +1,2 @@
|
||||
d10:created by18:qBittorrent v4.4.213:creation datei1650146462e4:infod9:file treed13:testfile1.txtd0:d6:lengthi33e11:pieces root32:à1
|
||||
x²ü„Ã3ÉGÏÐϾ¬0þ(2üËÁ,/Öneee12:meta versioni2e4:name13:testfile1.txt12:piece lengthi16384ee12:piece layersdee
|
Loading…
Reference in New Issue
Block a user