mirror of
https://github.com/rumanzo/bt2qbt.git
synced 2024-11-21 18:02:41 +01:00
Cesu8 (#44)
* 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:
parent
a52ae9559e
commit
fda659db74
2
Makefile
2
Makefile
@ -1,4 +1,4 @@
|
||||
gotag=1.18.1-bullseye
|
||||
gotag=1.21.4-bullseye
|
||||
|
||||
commit=$(shell git rev-parse HEAD)
|
||||
|
||||
|
2
go.mod
2
go.mod
@ -3,9 +3,9 @@ module github.com/rumanzo/bt2qbt
|
||||
go 1.16
|
||||
|
||||
require (
|
||||
github.com/crazytyper/go-cesu8 v0.0.0-20190615112902-270517b5a01c
|
||||
github.com/davecgh/go-spew v1.1.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/r3labs/diff/v2 v2.15.0
|
||||
github.com/stretchr/testify v1.7.0 // indirect
|
||||
|
4
go.sum
4
go.sum
@ -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/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
|
||||
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/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/jessevdk/go-flags v1.5.0 h1:1jKYvbxEjfUl0fmqTCOfonvskHHXMjBySTLW4y9LFvc=
|
||||
|
@ -1,6 +1,7 @@
|
||||
package transfer
|
||||
|
||||
import (
|
||||
"github.com/rumanzo/bt2qbt/pkg/helpers"
|
||||
"time"
|
||||
)
|
||||
|
||||
@ -8,12 +9,17 @@ func (transfer *TransferStructure) HandleStructures() {
|
||||
|
||||
if ok := transfer.ResumeItem.Targets; ok != nil {
|
||||
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
|
||||
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.AddedTime = transfer.ResumeItem.AddedOn
|
||||
transfer.Fastresume.CompletedTime = transfer.ResumeItem.CompletedOn
|
||||
|
@ -107,7 +107,7 @@ func HandleResumeItems(opts *options.Opts, resumeItems map[string]*utorrentStruc
|
||||
transferStruct.ResumeItem = resumeItem
|
||||
transferStruct.Replace = replaces
|
||||
transferStruct.Opts = opts
|
||||
go HandleResumeItem(key, &transferStruct, &chans, &wg)
|
||||
go HandleResumeItem(helpers.HandleCesu8(key), &transferStruct, &chans, &wg)
|
||||
}
|
||||
go func() {
|
||||
wg.Wait()
|
||||
|
@ -4,7 +4,9 @@ import (
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/r3labs/diff/v2"
|
||||
"github.com/rumanzo/bt2qbt/internal/options"
|
||||
"github.com/rumanzo/bt2qbt/pkg/fileHelpers"
|
||||
"github.com/rumanzo/bt2qbt/pkg/helpers"
|
||||
"github.com/rumanzo/bt2qbt/pkg/torrentStructures"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
@ -134,6 +136,16 @@ func TestHandleTorrentFilePath(t *testing.T) {
|
||||
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 {
|
||||
@ -160,3 +172,23 @@ func TestPath(t *testing.T) {
|
||||
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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ import (
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
//goland:noinspection GoNameStartsWithPackageName
|
||||
@ -76,7 +77,7 @@ func CreateEmptyNewTransferStructure() TransferStructure {
|
||||
|
||||
func (transfer *TransferStructure) HandleCaption() {
|
||||
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 {
|
||||
for _, label := range transfer.ResumeItem.Labels {
|
||||
if label != "" {
|
||||
transfer.Fastresume.QbtTags = append(transfer.Fastresume.QbtTags, label)
|
||||
transfer.Fastresume.QbtTags = append(transfer.Fastresume.QbtTags, helpers.HandleCesu8(label))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@ -136,7 +137,7 @@ func (transfer *TransferStructure) HandleTags() {
|
||||
}
|
||||
func (transfer *TransferStructure) HandleLabels() {
|
||||
if transfer.Opts.WithoutLabels == false {
|
||||
transfer.Fastresume.QBtCategory = transfer.ResumeItem.Label
|
||||
transfer.Fastresume.QBtCategory = helpers.HandleCesu8(transfer.ResumeItem.Label)
|
||||
} else {
|
||||
transfer.Fastresume.QBtCategory = ""
|
||||
}
|
||||
@ -155,9 +156,9 @@ func (transfer *TransferStructure) HandleTrackers() {
|
||||
index = "main"
|
||||
}
|
||||
if _, ok := trackersMap[index]; ok {
|
||||
trackersMap[index] = append(trackersMap[index], tracker)
|
||||
trackersMap[index] = append(trackersMap[index], helpers.HandleCesu8(tracker))
|
||||
} else {
|
||||
trackersMap[index] = []string{tracker}
|
||||
trackersMap[index] = []string{helpers.HandleCesu8(tracker)}
|
||||
}
|
||||
}
|
||||
if val, ok := trackersMap["main"]; ok {
|
||||
@ -268,40 +269,51 @@ func (transfer *TransferStructure) HandleSavePaths() {
|
||||
// qBtSavePath always has separator /, otherwise SavePath use os pathSeparator
|
||||
if transfer.Magnet {
|
||||
transfer.Fastresume.QBtContentLayout = "Original"
|
||||
transfer.Fastresume.QbtSavePath = fileHelpers.Normalize(transfer.ResumeItem.Path, "/")
|
||||
transfer.Fastresume.QbtSavePath = fileHelpers.Normalize(helpers.HandleCesu8(transfer.ResumeItem.Path), "/")
|
||||
} else {
|
||||
var torrentName string
|
||||
var torrentNameOriginal string
|
||||
if transfer.TorrentFile.Info.NameUTF8 != "" {
|
||||
torrentName = transfer.TorrentFile.Info.NameUTF8
|
||||
torrentName = helpers.HandleCesu8(transfer.TorrentFile.Info.NameUTF8)
|
||||
torrentNameOriginal = transfer.TorrentFile.Info.NameUTF8
|
||||
} 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 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.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 {
|
||||
transfer.Fastresume.MappedFiles = make([]string, maxIndex+1, maxIndex+1)
|
||||
for _, paths := range transfer.ResumeItem.Targets {
|
||||
index := paths[0].(int64)
|
||||
var pathParts []string
|
||||
if fileHelpers.IsAbs(paths[1].(string)) {
|
||||
pathParts = []string{fileHelpers.Normalize(paths[1].(string), transfer.Opts.PathSeparator)}
|
||||
if fileHelpers.IsAbs(helpers.HandleCesu8(paths[1].(string))) {
|
||||
pathParts = []string{fileHelpers.Normalize(helpers.HandleCesu8(paths[1].(string)), transfer.Opts.PathSeparator)}
|
||||
// if path is absolute just normalize it
|
||||
transfer.Fastresume.MappedFiles[index] = fileHelpers.Join(pathParts, transfer.Opts.PathSeparator)
|
||||
} else {
|
||||
pathParts = make([]string, len(paths)-1, len(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
|
||||
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]) != `/` {
|
||||
transfer.Fastresume.QbtSavePath += `/`
|
||||
}
|
||||
@ -310,33 +322,33 @@ func (transfer *TransferStructure) HandleSavePaths() {
|
||||
// NoSubfolder always has full mapped files
|
||||
// so we append all of them
|
||||
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
|
||||
if maxIndex := transfer.FindHighestIndexOfMappedFiles(); maxIndex >= 0 {
|
||||
for _, paths := range transfer.ResumeItem.Targets {
|
||||
index := paths[0].(int64)
|
||||
var pathParts []string
|
||||
if fileHelpers.IsAbs(paths[1].(string)) {
|
||||
pathParts = []string{fileHelpers.Normalize(paths[1].(string), transfer.Opts.PathSeparator)}
|
||||
if fileHelpers.IsAbs(helpers.HandleCesu8(paths[1].(string))) {
|
||||
pathParts = []string{fileHelpers.Normalize(helpers.HandleCesu8(paths[1].(string)), transfer.Opts.PathSeparator)}
|
||||
} else {
|
||||
pathParts = make([]string, len(paths)-1, len(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.QbtSavePath = fileHelpers.Normalize(transfer.ResumeItem.Path, "/")
|
||||
transfer.Fastresume.QbtSavePath = fileHelpers.Normalize(helpers.HandleCesu8(transfer.ResumeItem.Path), "/")
|
||||
}
|
||||
} else {
|
||||
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
|
||||
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]) != `/` {
|
||||
transfer.Fastresume.QbtSavePath += `/`
|
||||
}
|
||||
|
@ -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{
|
||||
Fastresume: &qBittorrentStructures.QBittorrentFastresume{},
|
||||
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{
|
||||
Fastresume: &qBittorrentStructures.QBittorrentFastresume{},
|
||||
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{
|
||||
Fastresume: &qBittorrentStructures.QBittorrentFastresume{},
|
||||
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 {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
@ -945,7 +1206,7 @@ func TestTransferStructure_HandleSavePaths(t *testing.T) {
|
||||
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, 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 {
|
||||
t.Fatalf("Unexpected error: structures are equal, but they shouldn't\n Got: %v\n", spew.Sdump(testCase.newTransferStructure.Fastresume))
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ package helpers
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"github.com/crazytyper/go-cesu8"
|
||||
"github.com/zeebo/bencode"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
@ -111,3 +112,10 @@ func GetStrings(trackers interface{}) []string {
|
||||
}
|
||||
return ntrackers
|
||||
}
|
||||
|
||||
func HandleCesu8(str string) string {
|
||||
if strings.Contains(str, "\xed\xa0") {
|
||||
return cesu8.DecodeString([]byte(str))
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
||||
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.
|
||||
Name string `bencode:"name"`
|
||||
NumComplete int64 `bencode:"num_complete"`
|
||||
NumDownloaded int64 `bencode:"num_downloaded"`
|
||||
NumIncomplete int64 `bencode:"num_incomplete"`
|
||||
|
1
test/data/normal_text 🆕 normal_text 🚜.txt.torrent
Normal file
1
test/data/normal_text 🆕 normal_text 🚜.txt.torrent
Normal 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>å̱›¦Lsӑ釘/»Óee
|
1
test/data/resume_emoji_clear.dat
Normal file
1
test/data/resume_emoji_clear.dat
Normal file
@ -0,0 +1 @@
|
||||
d49:normal_text <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> normal_text <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>.txt.torrentd4:test4:testee
|
Loading…
Reference in New Issue
Block a user