* cesu8 handler

* resume file cesu8 handle

* utf8 emoji filepaths

* cesu8 emoji filepaths and torrent names

* cesu8 emoji filepaths and torrent names handling
improve tests

* add fastresume Name field

* update golang ver

* handle torrent files with cesu8 encoded file paths and handle them as NoSubfolder torrents for better compatibility

* fix cesu8 named single file torrents
This commit is contained in:
Alexey Kostin 2023-11-25 22:47:40 +03:00 committed by GitHub
parent a52ae9559e
commit fda659db74
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 361 additions and 32 deletions

View File

@ -1,4 +1,4 @@
gotag=1.18.1-bullseye gotag=1.21.4-bullseye
commit=$(shell git rev-parse HEAD) commit=$(shell git rev-parse HEAD)

2
go.mod
View File

@ -3,9 +3,9 @@ module github.com/rumanzo/bt2qbt
go 1.16 go 1.16
require ( require (
github.com/crazytyper/go-cesu8 v0.0.0-20190615112902-270517b5a01c
github.com/davecgh/go-spew v1.1.0 github.com/davecgh/go-spew v1.1.0
github.com/fatih/color v1.13.0 github.com/fatih/color v1.13.0
github.com/go-ini/ini v1.64.0
github.com/jessevdk/go-flags v1.5.0 github.com/jessevdk/go-flags v1.5.0
github.com/r3labs/diff/v2 v2.15.0 github.com/r3labs/diff/v2 v2.15.0
github.com/stretchr/testify v1.7.0 // indirect github.com/stretchr/testify v1.7.0 // indirect

4
go.sum
View File

@ -1,9 +1,9 @@
github.com/crazytyper/go-cesu8 v0.0.0-20190615112902-270517b5a01c h1:LslEy3hCBNp2TfgmcmJBIIAprB51yH+AIBC3kVDxlGc=
github.com/crazytyper/go-cesu8 v0.0.0-20190615112902-270517b5a01c/go.mod h1:eWhedTyAcrUdtMYyEjm6HmjjwSGRre54xWeBBZGhhYc=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
github.com/go-ini/ini v1.64.0 h1:73w/ADE+yoYjfu4BlI/LaEMe9Do1zOQ6qPt1du4uikI=
github.com/go-ini/ini v1.64.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg= github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/jessevdk/go-flags v1.5.0 h1:1jKYvbxEjfUl0fmqTCOfonvskHHXMjBySTLW4y9LFvc= github.com/jessevdk/go-flags v1.5.0 h1:1jKYvbxEjfUl0fmqTCOfonvskHHXMjBySTLW4y9LFvc=

View File

@ -1,6 +1,7 @@
package transfer package transfer
import ( import (
"github.com/rumanzo/bt2qbt/pkg/helpers"
"time" "time"
) )
@ -8,12 +9,17 @@ func (transfer *TransferStructure) HandleStructures() {
if ok := transfer.ResumeItem.Targets; ok != nil { if ok := transfer.ResumeItem.Targets; ok != nil {
for _, entry := range transfer.ResumeItem.Targets { for _, entry := range transfer.ResumeItem.Targets {
transfer.Targets[entry[0].(int64)] = entry[1].(string) transfer.Targets[entry[0].(int64)] = helpers.HandleCesu8(entry[1].(string))
} }
} }
// if torrent name was renamed, add modified name // if torrent name was renamed, add modified name
transfer.HandleCaption() transfer.HandleCaption()
if transfer.TorrentFile.Info.NameUTF8 != "" {
transfer.Fastresume.Name = helpers.HandleCesu8(transfer.TorrentFile.Info.NameUTF8)
} else {
transfer.Fastresume.Name = helpers.HandleCesu8(transfer.TorrentFile.Info.Name)
}
transfer.Fastresume.ActiveTime = transfer.ResumeItem.Runtime transfer.Fastresume.ActiveTime = transfer.ResumeItem.Runtime
transfer.Fastresume.AddedTime = transfer.ResumeItem.AddedOn transfer.Fastresume.AddedTime = transfer.ResumeItem.AddedOn
transfer.Fastresume.CompletedTime = transfer.ResumeItem.CompletedOn transfer.Fastresume.CompletedTime = transfer.ResumeItem.CompletedOn

View File

@ -107,7 +107,7 @@ func HandleResumeItems(opts *options.Opts, resumeItems map[string]*utorrentStruc
transferStruct.ResumeItem = resumeItem transferStruct.ResumeItem = resumeItem
transferStruct.Replace = replaces transferStruct.Replace = replaces
transferStruct.Opts = opts transferStruct.Opts = opts
go HandleResumeItem(key, &transferStruct, &chans, &wg) go HandleResumeItem(helpers.HandleCesu8(key), &transferStruct, &chans, &wg)
} }
go func() { go func() {
wg.Wait() wg.Wait()

View File

@ -4,7 +4,9 @@ import (
"github.com/davecgh/go-spew/spew" "github.com/davecgh/go-spew/spew"
"github.com/r3labs/diff/v2" "github.com/r3labs/diff/v2"
"github.com/rumanzo/bt2qbt/internal/options" "github.com/rumanzo/bt2qbt/internal/options"
"github.com/rumanzo/bt2qbt/pkg/fileHelpers"
"github.com/rumanzo/bt2qbt/pkg/helpers" "github.com/rumanzo/bt2qbt/pkg/helpers"
"github.com/rumanzo/bt2qbt/pkg/torrentStructures"
"reflect" "reflect"
"testing" "testing"
) )
@ -134,6 +136,16 @@ func TestHandleTorrentFilePath(t *testing.T) {
Opts: &options.Opts{BitDir: `C:\\temp`}, Opts: &options.Opts{BitDir: `C:\\temp`},
}, },
}, },
{
name: "007 Check emoji",
key: "normal_text \xf0\x9f\x86\x95 normal_text \xf0\x9f\x9a\x9c.txt.torrent",
newTransferStructure: &TransferStructure{Opts: &options.Opts{BitDir: `C:\\temp`}},
expected: &TransferStructure{
TorrentFilePath: "C:/temp/normal_text \xf0\x9f\x86\x95 normal_text \xf0\x9f\x9a\x9c.txt.torrent",
TorrentFileName: "normal_text \xf0\x9f\x86\x95 normal_text \xf0\x9f\x9a\x9c.txt.torrent",
Opts: &options.Opts{BitDir: `C:\\temp`},
},
},
} }
for _, testCase := range cases { for _, testCase := range cases {
@ -160,3 +172,23 @@ func TestPath(t *testing.T) {
t.Fatalf("Can't decode torrent file with error: %v", err) t.Fatalf("Can't decode torrent file with error: %v", err)
} }
} }
func TestEmojiResumeDecode(t *testing.T) {
resumeFilePath := "../../test/data/resume_emoji_clear.dat"
torrentsDir := "../../test/data/"
resumeFile := map[string]interface{}{}
err := helpers.DecodeTorrentFile(resumeFilePath, resumeFile)
if err != nil {
t.Fatalf("Can't decode torrent file with error: %v", err)
}
for k, _ := range resumeFile {
torrentFile := torrentStructures.Torrent{}
err := helpers.DecodeTorrentFile(fileHelpers.Join([]string{torrentsDir, helpers.HandleCesu8(k)}, "/"), &torrentFile)
if err != nil {
t.Fatalf("Can't decode torrent file with error: %v", err)
}
if torrentFile.CreationDate == 0 {
t.Fatal("Decoded values is wrong")
}
}
}

View File

@ -15,6 +15,7 @@ import (
"regexp" "regexp"
"strings" "strings"
"time" "time"
"unicode/utf8"
) )
//goland:noinspection GoNameStartsWithPackageName //goland:noinspection GoNameStartsWithPackageName
@ -76,7 +77,7 @@ func CreateEmptyNewTransferStructure() TransferStructure {
func (transfer *TransferStructure) HandleCaption() { func (transfer *TransferStructure) HandleCaption() {
if transfer.ResumeItem.Caption != "" { if transfer.ResumeItem.Caption != "" {
transfer.Fastresume.QbtName = transfer.ResumeItem.Caption transfer.Fastresume.QbtName = helpers.HandleCesu8(transfer.ResumeItem.Caption)
} }
} }
@ -127,7 +128,7 @@ func (transfer *TransferStructure) HandleTags() {
if transfer.Opts.WithoutTags == false && transfer.ResumeItem.Labels != nil { if transfer.Opts.WithoutTags == false && transfer.ResumeItem.Labels != nil {
for _, label := range transfer.ResumeItem.Labels { for _, label := range transfer.ResumeItem.Labels {
if label != "" { if label != "" {
transfer.Fastresume.QbtTags = append(transfer.Fastresume.QbtTags, label) transfer.Fastresume.QbtTags = append(transfer.Fastresume.QbtTags, helpers.HandleCesu8(label))
} }
} }
} else { } else {
@ -136,7 +137,7 @@ func (transfer *TransferStructure) HandleTags() {
} }
func (transfer *TransferStructure) HandleLabels() { func (transfer *TransferStructure) HandleLabels() {
if transfer.Opts.WithoutLabels == false { if transfer.Opts.WithoutLabels == false {
transfer.Fastresume.QBtCategory = transfer.ResumeItem.Label transfer.Fastresume.QBtCategory = helpers.HandleCesu8(transfer.ResumeItem.Label)
} else { } else {
transfer.Fastresume.QBtCategory = "" transfer.Fastresume.QBtCategory = ""
} }
@ -155,9 +156,9 @@ func (transfer *TransferStructure) HandleTrackers() {
index = "main" index = "main"
} }
if _, ok := trackersMap[index]; ok { if _, ok := trackersMap[index]; ok {
trackersMap[index] = append(trackersMap[index], tracker) trackersMap[index] = append(trackersMap[index], helpers.HandleCesu8(tracker))
} else { } else {
trackersMap[index] = []string{tracker} trackersMap[index] = []string{helpers.HandleCesu8(tracker)}
} }
} }
if val, ok := trackersMap["main"]; ok { if val, ok := trackersMap["main"]; ok {
@ -268,40 +269,51 @@ func (transfer *TransferStructure) HandleSavePaths() {
// qBtSavePath always has separator /, otherwise SavePath use os pathSeparator // qBtSavePath always has separator /, otherwise SavePath use os pathSeparator
if transfer.Magnet { if transfer.Magnet {
transfer.Fastresume.QBtContentLayout = "Original" transfer.Fastresume.QBtContentLayout = "Original"
transfer.Fastresume.QbtSavePath = fileHelpers.Normalize(transfer.ResumeItem.Path, "/") transfer.Fastresume.QbtSavePath = fileHelpers.Normalize(helpers.HandleCesu8(transfer.ResumeItem.Path), "/")
} else { } else {
var torrentName string var torrentName string
var torrentNameOriginal string
if transfer.TorrentFile.Info.NameUTF8 != "" { if transfer.TorrentFile.Info.NameUTF8 != "" {
torrentName = transfer.TorrentFile.Info.NameUTF8 torrentName = helpers.HandleCesu8(transfer.TorrentFile.Info.NameUTF8)
torrentNameOriginal = transfer.TorrentFile.Info.NameUTF8
} else { } else {
torrentName = transfer.TorrentFile.Info.Name torrentName = helpers.HandleCesu8(transfer.TorrentFile.Info.Name)
torrentNameOriginal = transfer.TorrentFile.Info.Name
} }
lastPathName := fileHelpers.Base(transfer.ResumeItem.Path) lastPathName := fileHelpers.Base(helpers.HandleCesu8(transfer.ResumeItem.Path))
if len(transfer.TorrentFile.GetFileList()) > 0 { if len(transfer.TorrentFile.GetFileList()) > 0 {
if lastPathName == torrentName { var cesu8FilesExists bool
for _, filePath := range transfer.TorrentFile.GetFileList() {
cesuEncodedFilepath := helpers.HandleCesu8(filePath)
if utf8.RuneCountInString(filePath) != utf8.RuneCountInString(cesuEncodedFilepath) {
cesu8FilesExists = true
break
}
}
if lastPathName == torrentName && !cesu8FilesExists {
transfer.Fastresume.QBtContentLayout = "Original" transfer.Fastresume.QBtContentLayout = "Original"
transfer.Fastresume.QbtSavePath = fileHelpers.CutLastPath(transfer.ResumeItem.Path, transfer.Opts.PathSeparator) transfer.Fastresume.QbtSavePath = fileHelpers.CutLastPath(helpers.HandleCesu8(transfer.ResumeItem.Path), transfer.Opts.PathSeparator)
if maxIndex := transfer.FindHighestIndexOfMappedFiles(); maxIndex >= 0 { if maxIndex := transfer.FindHighestIndexOfMappedFiles(); maxIndex >= 0 {
transfer.Fastresume.MappedFiles = make([]string, maxIndex+1, maxIndex+1) transfer.Fastresume.MappedFiles = make([]string, maxIndex+1, maxIndex+1)
for _, paths := range transfer.ResumeItem.Targets { for _, paths := range transfer.ResumeItem.Targets {
index := paths[0].(int64) index := paths[0].(int64)
var pathParts []string var pathParts []string
if fileHelpers.IsAbs(paths[1].(string)) { if fileHelpers.IsAbs(helpers.HandleCesu8(paths[1].(string))) {
pathParts = []string{fileHelpers.Normalize(paths[1].(string), transfer.Opts.PathSeparator)} pathParts = []string{fileHelpers.Normalize(helpers.HandleCesu8(paths[1].(string)), transfer.Opts.PathSeparator)}
// if path is absolute just normalize it // if path is absolute just normalize it
transfer.Fastresume.MappedFiles[index] = fileHelpers.Join(pathParts, transfer.Opts.PathSeparator) transfer.Fastresume.MappedFiles[index] = fileHelpers.Join(pathParts, transfer.Opts.PathSeparator)
} else { } else {
pathParts = make([]string, len(paths)-1, len(paths)-1) pathParts = make([]string, len(paths)-1, len(paths)-1)
for num, part := range paths[1:] { for num, part := range paths[1:] {
pathParts[num] = part.(string) pathParts[num] = helpers.HandleCesu8(part.(string))
} }
// we have to append torrent name(from torrent file) at the top of path // we have to append torrent name(from torrent file) at the top of path
transfer.Fastresume.MappedFiles[index] = fileHelpers.Join(append([]string{torrentName}, pathParts...), transfer.Opts.PathSeparator) transfer.Fastresume.MappedFiles[index] = fileHelpers.Join(append([]string{torrentName}, pathParts...), transfer.Opts.PathSeparator)
} }
} }
} }
transfer.Fastresume.QbtSavePath = fileHelpers.CutLastPath(transfer.ResumeItem.Path, "/") transfer.Fastresume.QbtSavePath = fileHelpers.CutLastPath(helpers.HandleCesu8(transfer.ResumeItem.Path), "/")
if string(transfer.Fastresume.QbtSavePath[len(transfer.Fastresume.QbtSavePath)-1]) != `/` { if string(transfer.Fastresume.QbtSavePath[len(transfer.Fastresume.QbtSavePath)-1]) != `/` {
transfer.Fastresume.QbtSavePath += `/` transfer.Fastresume.QbtSavePath += `/`
} }
@ -310,33 +322,33 @@ func (transfer *TransferStructure) HandleSavePaths() {
// NoSubfolder always has full mapped files // NoSubfolder always has full mapped files
// so we append all of them // so we append all of them
for _, filePath := range transfer.TorrentFile.GetFileList() { for _, filePath := range transfer.TorrentFile.GetFileList() {
transfer.Fastresume.MappedFiles = append(transfer.Fastresume.MappedFiles, fileHelpers.Normalize(filePath, transfer.Opts.PathSeparator)) transfer.Fastresume.MappedFiles = append(transfer.Fastresume.MappedFiles, fileHelpers.Normalize(helpers.HandleCesu8(filePath), transfer.Opts.PathSeparator))
} }
// and then doing remap if resumeItem contain target field // and then doing remap if resumeItem contain target field
if maxIndex := transfer.FindHighestIndexOfMappedFiles(); maxIndex >= 0 { if maxIndex := transfer.FindHighestIndexOfMappedFiles(); maxIndex >= 0 {
for _, paths := range transfer.ResumeItem.Targets { for _, paths := range transfer.ResumeItem.Targets {
index := paths[0].(int64) index := paths[0].(int64)
var pathParts []string var pathParts []string
if fileHelpers.IsAbs(paths[1].(string)) { if fileHelpers.IsAbs(helpers.HandleCesu8(paths[1].(string))) {
pathParts = []string{fileHelpers.Normalize(paths[1].(string), transfer.Opts.PathSeparator)} pathParts = []string{fileHelpers.Normalize(helpers.HandleCesu8(paths[1].(string)), transfer.Opts.PathSeparator)}
} else { } else {
pathParts = make([]string, len(paths)-1, len(paths)-1) pathParts = make([]string, len(paths)-1, len(paths)-1)
for num, part := range paths[1:] { for num, part := range paths[1:] {
pathParts[num] = part.(string) pathParts[num] = helpers.HandleCesu8(part.(string))
} }
} }
transfer.Fastresume.MappedFiles[index] = fileHelpers.Join(pathParts, transfer.Opts.PathSeparator) transfer.Fastresume.MappedFiles[index] = fileHelpers.Join(pathParts, transfer.Opts.PathSeparator)
} }
} }
transfer.Fastresume.QbtSavePath = fileHelpers.Normalize(transfer.ResumeItem.Path, "/") transfer.Fastresume.QbtSavePath = fileHelpers.Normalize(helpers.HandleCesu8(transfer.ResumeItem.Path), "/")
} }
} else { } else {
transfer.Fastresume.QBtContentLayout = "Original" // utorrent\bittorrent don't support create subfolders for torrents with single file transfer.Fastresume.QBtContentLayout = "Original" // utorrent\bittorrent don't support create subfolders for torrents with single file
if lastPathName != torrentName { if lastPathName != torrentNameOriginal {
//it means that we have renamed path and targets item, and should have mapped files //it means that we have renamed path and targets item, and should have mapped files
transfer.Fastresume.MappedFiles = []string{lastPathName} transfer.Fastresume.MappedFiles = []string{lastPathName}
} }
transfer.Fastresume.QbtSavePath = fileHelpers.CutLastPath(transfer.ResumeItem.Path, `/`) transfer.Fastresume.QbtSavePath = fileHelpers.CutLastPath(helpers.HandleCesu8(transfer.ResumeItem.Path), `/`)
if string(transfer.Fastresume.QbtSavePath[len(transfer.Fastresume.QbtSavePath)-1]) != `/` { if string(transfer.Fastresume.QbtSavePath[len(transfer.Fastresume.QbtSavePath)-1]) != `/` {
transfer.Fastresume.QbtSavePath += `/` transfer.Fastresume.QbtSavePath += `/`
} }

View File

@ -659,7 +659,7 @@ func TestTransferStructure_HandleSavePaths(t *testing.T) {
}, },
}, },
{ {
name: "021 Test torrent with windows folder (Original) path without replaces. Moved files with absolute paths. Windows share", name: "021 Test torrent with windows share folder (Original) path without replaces. Moved files with absolute paths. Windows share",
newTransferStructure: &TransferStructure{ newTransferStructure: &TransferStructure{
Fastresume: &qBittorrentStructures.QBittorrentFastresume{}, Fastresume: &qBittorrentStructures.QBittorrentFastresume{},
ResumeItem: &utorrentStructs.ResumeItem{ ResumeItem: &utorrentStructs.ResumeItem{
@ -709,7 +709,7 @@ func TestTransferStructure_HandleSavePaths(t *testing.T) {
}, },
}, },
{ {
name: "022 Test torrent with windows folder (NoSubfolder) path without replaces. Moved files with absolute paths", name: "022 Test torrent with windows share folder (NoSubfolder) path without replaces. Moved files with absolute paths",
newTransferStructure: &TransferStructure{ newTransferStructure: &TransferStructure{
Fastresume: &qBittorrentStructures.QBittorrentFastresume{}, Fastresume: &qBittorrentStructures.QBittorrentFastresume{},
ResumeItem: &utorrentStructs.ResumeItem{ ResumeItem: &utorrentStructs.ResumeItem{
@ -902,7 +902,7 @@ func TestTransferStructure_HandleSavePaths(t *testing.T) {
}, },
}, },
{ {
name: "027 Test torrent with signle file torrent and savepath in rootdirectory", name: "027 Test torrent with multi file torrent and savepath in rootdirectory",
newTransferStructure: &TransferStructure{ newTransferStructure: &TransferStructure{
Fastresume: &qBittorrentStructures.QBittorrentFastresume{}, Fastresume: &qBittorrentStructures.QBittorrentFastresume{},
ResumeItem: &utorrentStructs.ResumeItem{ ResumeItem: &utorrentStructs.ResumeItem{
@ -930,6 +930,267 @@ func TestTransferStructure_HandleSavePaths(t *testing.T) {
}, },
}, },
}, },
{
name: "028 Test torrent with windows folder (original) path without replaces. Emoji utf8 in file and name",
newTransferStructure: &TransferStructure{
Fastresume: &qBittorrentStructures.QBittorrentFastresume{},
ResumeItem: &utorrentStructs.ResumeItem{
Path: "D:\\torrents\\test_torrent \xf0\x9f\x86\x95",
Targets: [][]interface{}{
[]interface{}{
int64(0),
"E:\\somedir1 \xf0\x9f\x86\x95\\\xf0\x9f\x86\x95 renamed_test_torrent2.txt",
},
[]interface{}{
int64(1),
"\\\\somedir\\somedir4 \xf0\x9f\x86\x95\\\xf0\x9f\x86\x95 renamed_test_torrent3.txt",
},
[]interface{}{
int64(2),
"renamed \xf0\x9f\x86\x95 file1.txt",
},
},
},
TorrentFile: &torrentStructures.Torrent{
Info: &torrentStructures.TorrentInfo{
Name: "test_torrent \xf0\x9f\x86\x95",
Files: []*torrentStructures.TorrentFile{
&torrentStructures.TorrentFile{Path: []string{"dir1", "\xf0\x9f\x86\x95 file1.txt"}},
&torrentStructures.TorrentFile{Path: []string{"\xf0\x9f\x86\x95", "file2.txt"}},
&torrentStructures.TorrentFile{Path: []string{"file0 \xf0\x9f\x86\x95.txt"}},
},
},
},
Opts: &options.Opts{PathSeparator: `\`},
},
expected: &TransferStructure{
Fastresume: &qBittorrentStructures.QBittorrentFastresume{
QbtSavePath: `D:/torrents/`,
SavePath: `D:\torrents\`,
QBtContentLayout: "Original",
MappedFiles: []string{
"E:\\somedir1 \xf0\x9f\x86\x95\\\xf0\x9f\x86\x95 renamed_test_torrent2.txt",
"\\\\somedir\\somedir4 \xf0\x9f\x86\x95\\\xf0\x9f\x86\x95 renamed_test_torrent3.txt",
"test_torrent \xf0\x9f\x86\x95\\renamed \xf0\x9f\x86\x95 file1.txt",
},
},
},
},
{
name: "029 Test torrent with windows folder (NoSubfolder) with renamed files. Emoji utf8 in file and torrent name",
newTransferStructure: &TransferStructure{
Fastresume: &qBittorrentStructures.QBittorrentFastresume{},
ResumeItem: &utorrentStructs.ResumeItem{Path: "D:\\torrents\\renamed test_torrent \xf0\x9f\x86\x95"},
TorrentFile: &torrentStructures.Torrent{
Info: &torrentStructures.TorrentInfo{
Name: "test_torrent \xf0\x9f\x86\x95",
Files: []*torrentStructures.TorrentFile{
&torrentStructures.TorrentFile{Path: []string{"dir1", "\xf0\x9f\x86\x95 file1.txt"}},
&torrentStructures.TorrentFile{Path: []string{"\xf0\x9f\x86\x95", "file2.txt"}},
&torrentStructures.TorrentFile{Path: []string{"file0 \xf0\x9f\x86\x95.txt"}},
},
},
},
Opts: &options.Opts{PathSeparator: `\`},
},
expected: &TransferStructure{
Fastresume: &qBittorrentStructures.QBittorrentFastresume{
QbtSavePath: "D:/torrents/renamed test_torrent \xf0\x9f\x86\x95",
SavePath: "D:\\torrents\\renamed test_torrent \xf0\x9f\x86\x95",
QBtContentLayout: "NoSubfolder",
MappedFiles: []string{
"dir1\\\xf0\x9f\x86\x95 file1.txt",
"\xf0\x9f\x86\x95\\file2.txt",
"file0 \xf0\x9f\x86\x95.txt",
},
},
},
},
{
name: "030 Test torrent with windows folder (original) path with renamed files. Emoji cesu8 in file and name",
newTransferStructure: &TransferStructure{
Fastresume: &qBittorrentStructures.QBittorrentFastresume{},
ResumeItem: &utorrentStructs.ResumeItem{
Path: "D:\\torrents\\test_torrent \xed\xa0\xbc\xed\xb6\x95",
Targets: [][]interface{}{
[]interface{}{
int64(0),
"E:\\somedir1 \xed\xa0\xbc\xed\xb6\x95\\\xed\xa0\xbc\xed\xb6\x95 renamed_test_torrent2.txt",
},
[]interface{}{
int64(1),
"\\\\somedir\\somedir4 \xed\xa0\xbc\xed\xb6\x95\\\xed\xa0\xbc\xed\xb6\x95 renamed_test_torrent3.txt",
},
[]interface{}{
int64(2),
"renamed \xed\xa0\xbc\xed\xb6\x95 file1.txt",
},
},
},
TorrentFile: &torrentStructures.Torrent{
Info: &torrentStructures.TorrentInfo{
Name: "test_torrent \xed\xa0\xbc\xed\xb6\x95",
Files: []*torrentStructures.TorrentFile{
&torrentStructures.TorrentFile{Path: []string{"dir1", "\xed\xa0\xbc\xed\xb6\x95 file1.txt"}},
&torrentStructures.TorrentFile{Path: []string{"\xed\xa0\xbc\xed\xb6\x95", "file2.txt"}},
&torrentStructures.TorrentFile{Path: []string{"file0 \xed\xa0\xbc\xed\xb6\x95.txt"}},
},
},
},
Opts: &options.Opts{PathSeparator: `\`},
},
expected: &TransferStructure{
Fastresume: &qBittorrentStructures.QBittorrentFastresume{
QbtSavePath: "D:/torrents/test_torrent \xf0\x9f\x86\x95",
SavePath: "D:\\torrents\\test_torrent \xf0\x9f\x86\x95",
QBtContentLayout: "NoSubfolder",
MappedFiles: []string{
"E:\\somedir1 \xf0\x9f\x86\x95\\\xf0\x9f\x86\x95 renamed_test_torrent2.txt",
"\\\\somedir\\somedir4 \xf0\x9f\x86\x95\\\xf0\x9f\x86\x95 renamed_test_torrent3.txt",
"renamed \xf0\x9f\x86\x95 file1.txt",
},
},
},
},
{
name: "031 Test torrent with windows folder (NoSubfolder) with renamed files. Emoji cesu8 in file and torrent name",
newTransferStructure: &TransferStructure{
Fastresume: &qBittorrentStructures.QBittorrentFastresume{},
ResumeItem: &utorrentStructs.ResumeItem{Path: "D:\\torrents\\renamed test_torrent \xed\xa0\xbc\xed\xb6\x95"},
TorrentFile: &torrentStructures.Torrent{
Info: &torrentStructures.TorrentInfo{
Name: "test_torrent \xed\xa0\xbc\xed\xb6\x95",
Files: []*torrentStructures.TorrentFile{
&torrentStructures.TorrentFile{Path: []string{"dir1", "\xed\xa0\xbc\xed\xb6\x95 file1.txt"}},
&torrentStructures.TorrentFile{Path: []string{"\xed\xa0\xbc\xed\xb6\x95", "file2.txt"}},
&torrentStructures.TorrentFile{Path: []string{"file0 \xed\xa0\xbc\xed\xb6\x95.txt"}},
},
},
},
Opts: &options.Opts{PathSeparator: `\`},
},
expected: &TransferStructure{
Fastresume: &qBittorrentStructures.QBittorrentFastresume{
QbtSavePath: "D:/torrents/renamed test_torrent \xf0\x9f\x86\x95",
SavePath: "D:\\torrents\\renamed test_torrent \xf0\x9f\x86\x95",
QBtContentLayout: "NoSubfolder",
MappedFiles: []string{
"dir1\\\xf0\x9f\x86\x95 file1.txt",
"\xf0\x9f\x86\x95\\file2.txt",
"file0 \xf0\x9f\x86\x95.txt",
},
},
},
},
{
name: "032 Test torrent with windows folder without renamed files. Emoji cesu8 in file and torrent name",
newTransferStructure: &TransferStructure{
Fastresume: &qBittorrentStructures.QBittorrentFastresume{},
ResumeItem: &utorrentStructs.ResumeItem{Path: "D:\\torrents\\test_torrent"},
TorrentFile: &torrentStructures.Torrent{
Info: &torrentStructures.TorrentInfo{
Name: "test_torrent",
Files: []*torrentStructures.TorrentFile{
&torrentStructures.TorrentFile{Path: []string{"dir1", "\xed\xa0\xbc\xed\xb6\x95 file1.txt"}},
&torrentStructures.TorrentFile{Path: []string{"\xed\xa0\xbc\xed\xb6\x95", "file2.txt"}},
&torrentStructures.TorrentFile{Path: []string{"file0 \xed\xa0\xbc\xed\xb6\x95.txt"}},
&torrentStructures.TorrentFile{Path: []string{"file1.txt"}},
&torrentStructures.TorrentFile{Path: []string{"file2 \xed\xa0\xbc\xed\xb6\x95.txt"}},
},
},
},
Opts: &options.Opts{PathSeparator: `\`},
},
expected: &TransferStructure{
Fastresume: &qBittorrentStructures.QBittorrentFastresume{
QbtSavePath: "D:/torrents/test_torrent",
SavePath: "D:\\torrents\\test_torrent",
QBtContentLayout: "NoSubfolder",
MappedFiles: []string{
"dir1\\\xf0\x9f\x86\x95 file1.txt",
"\xf0\x9f\x86\x95\\file2.txt",
"file0 \xf0\x9f\x86\x95.txt",
`file1.txt`,
"file2 \xf0\x9f\x86\x95.txt",
},
},
},
},
{
name: "033 Test torrent with windows folder with renamed files. Emoji cesu8 in file and torrent name",
newTransferStructure: &TransferStructure{
Fastresume: &qBittorrentStructures.QBittorrentFastresume{},
ResumeItem: &utorrentStructs.ResumeItem{
Path: "D:\\torrents\\test_torrent",
Targets: [][]interface{}{
[]interface{}{
int64(0),
"E:\\somedir1 \xed\xa0\xbc\xed\xb6\x95\\\xed\xa0\xbc\xed\xb6\x95 renamed_test_torrent2.txt",
},
[]interface{}{
int64(1),
"\\\\somedir\\somedir4 \xed\xa0\xbc\xed\xb6\x95\\\xed\xa0\xbc\xed\xb6\x95 renamed_test_torrent3.txt",
},
[]interface{}{
int64(3),
"renamed \xed\xa0\xbc\xed\xb6\x95 file1.txt",
},
},
},
TorrentFile: &torrentStructures.Torrent{
Info: &torrentStructures.TorrentInfo{
Name: "test_torrent",
Files: []*torrentStructures.TorrentFile{
&torrentStructures.TorrentFile{Path: []string{"dir1", "\xed\xa0\xbc\xed\xb6\x95 file1.txt"}},
&torrentStructures.TorrentFile{Path: []string{"\xed\xa0\xbc\xed\xb6\x95", "file2.txt"}},
&torrentStructures.TorrentFile{Path: []string{"file0 \xed\xa0\xbc\xed\xb6\x95.txt"}},
&torrentStructures.TorrentFile{Path: []string{"file1.txt"}},
&torrentStructures.TorrentFile{Path: []string{"file2 \xed\xa0\xbc\xed\xb6\x95.txt"}},
&torrentStructures.TorrentFile{Path: []string{"file4.txt"}},
},
},
},
Opts: &options.Opts{PathSeparator: `\`},
},
expected: &TransferStructure{
Fastresume: &qBittorrentStructures.QBittorrentFastresume{
QbtSavePath: "D:/torrents/test_torrent",
SavePath: "D:\\torrents\\test_torrent",
QBtContentLayout: "NoSubfolder",
MappedFiles: []string{
"E:\\somedir1 \xf0\x9f\x86\x95\\\xf0\x9f\x86\x95 renamed_test_torrent2.txt",
"\\\\somedir\\somedir4 \xf0\x9f\x86\x95\\\xf0\x9f\x86\x95 renamed_test_torrent3.txt",
"file0 \xf0\x9f\x86\x95.txt",
"renamed \xf0\x9f\x86\x95 file1.txt",
"file2 \xf0\x9f\x86\x95.txt",
"file4.txt",
},
},
},
},
{
name: "034 Test torrent with windows single nofolder (original) path without replaces. Cesu8 emoji",
newTransferStructure: &TransferStructure{
Fastresume: &qBittorrentStructures.QBittorrentFastresume{},
ResumeItem: &utorrentStructs.ResumeItem{Path: "D:\\torrents\\test_torrent \xed\xa0\xbc\xed\xb6\x95.txt"},
TorrentFile: &torrentStructures.Torrent{
Info: &torrentStructures.TorrentInfo{
Name: "test_torrent \xed\xa0\xbc\xed\xb6\x95.txt",
},
},
Opts: &options.Opts{PathSeparator: `\`},
},
expected: &TransferStructure{
Fastresume: &qBittorrentStructures.QBittorrentFastresume{
QbtSavePath: `D:/torrents/`,
SavePath: `D:\torrents\`,
MappedFiles: []string{
"test_torrent \xf0\x9f\x86\x95.txt",
},
QBtContentLayout: "Original",
},
},
},
} }
for _, testCase := range cases { for _, testCase := range cases {
t.Run(testCase.name, func(t *testing.T) { t.Run(testCase.name, func(t *testing.T) {
@ -945,7 +1206,7 @@ func TestTransferStructure_HandleSavePaths(t *testing.T) {
if err != nil { if err != nil {
t.Error(err.Error()) 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, testCase.expected.Fastresume, spew.Sdump(changes)) t.Fatalf("Unexpected error: opts isn't equal:\n Got: %#v \n Expect %#v \n Diff: %v\n", testCase.newTransferStructure.Fastresume, testCase.expected.Fastresume, spew.Sdump(changes))
} else if equal && testCase.mustFail { } 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)) t.Fatalf("Unexpected error: structures are equal, but they shouldn't\n Got: %v\n", spew.Sdump(testCase.newTransferStructure.Fastresume))
} }

View File

@ -3,6 +3,7 @@ package helpers
import ( import (
"bufio" "bufio"
"bytes" "bytes"
"github.com/crazytyper/go-cesu8"
"github.com/zeebo/bencode" "github.com/zeebo/bencode"
"io" "io"
"io/ioutil" "io/ioutil"
@ -111,3 +112,10 @@ func GetStrings(trackers interface{}) []string {
} }
return ntrackers return ntrackers
} }
func HandleCesu8(str string) string {
if strings.Contains(str, "\xed\xa0") {
return cesu8.DecodeString([]byte(str))
}
return str
}

View File

@ -57,3 +57,10 @@ func TestDecodeTorrentFile(t *testing.T) {
}) })
} }
} }
func TestEmojiCesu8(t *testing.T) {
cesu8 := "normal_text \xed\xa0\xbc\xed\xb6\x95 normal_text \xed\xa0\xbd\xed\xba\x9c.txt.torrent"
utf8 := "normal_text \xf0\x9f\x86\x95 normal_text \xf0\x9f\x9a\x9c.txt.torrent"
if utf8 != HandleCesu8(cesu8) {
t.Fatalf("Cesu8 to utf-8 transformation fail")
}
}

View File

@ -30,6 +30,7 @@ type QBittorrentFastresume struct {
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. 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. 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. MaxUploads int64 `bencode:"max_uploads"` // integer. The max number of unchoked peers this torrent may have, if a limit is set.
Name string `bencode:"name"`
NumComplete int64 `bencode:"num_complete"` NumComplete int64 `bencode:"num_complete"`
NumDownloaded int64 `bencode:"num_downloaded"` NumDownloaded int64 `bencode:"num_downloaded"`
NumIncomplete int64 `bencode:"num_incomplete"` NumIncomplete int64 `bencode:"num_incomplete"`

View File

@ -0,0 +1 @@
d8:announce44:udp://tracker.openbittorrent.com:80/announce13:announce-listll44:udp://tracker.openbittorrent.com:80/announceel42:udp://tracker.opentrackr.org:1337/announceee10:created by12:uTorrent/3.613:creation datei1700849684e8:encoding5:UTF-84:infod6:lengthi4e4:name41:normal_text 🆕 normal_text 🚜.txt12:piece lengthi16384e6:pieces20:©J<C2A9>å̱¦L釘/»Óee

View File

@ -0,0 +1 @@
d49:normal_text <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> normal_text <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>.txt.torrentd4:test4:testee