Fix windows prohibited simbols (#46)

* handle windows fs prohibited symbols

* fix abs path symbols replace

* more readable code

* better replacer
new replacer function with tests

* return zero files from torrent info file tree if there are single file torrent

* don't harm torrent file function, handle filelist size in another places

* more accurate handling single files

* use mapped files for directories or torrent names with space on ending

* normalize backslases too

* Don't normalize cesu8\prohibited symbols on torrent name if torrent file contain several files. But normalize for single file torrent and normalize last space character for both

* Better torrent name and torrent file paths handling
helpers
tests

* move torrents functions to torrents functions
cache single flag

* update README.md

* fix deprecation warnings
This commit is contained in:
Alexey Kostin 2023-12-05 21:17:43 +03:00 committed by GitHub
parent a25c2670ff
commit 0b305dd2f9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 1083 additions and 104 deletions

View File

@ -1,3 +1,5 @@
![GitHub all releases](https://img.shields.io/github/downloads/rumanzo/bt2qbt/total)
# bt2qbt
bt2qbt is cli tool for export from uTorrent\Bittorrent into qBittorrent (convert)
@ -5,7 +7,7 @@ bt2qbt is cli tool for export from uTorrent\Bittorrent into qBittorrent (convert
> Actual version tested with uTorrent 3.5.5 (build 46206) and qBittorrent 4.4.2. It should work with older version utorrent and newer version of qBittorrent, but it isn't tested.
> [!IMPORTANT]
> In most cases just enough run app. For windows users double click on downloaded exe file. But read notices and warnings below
> In most cases just enough run app. For Windows users double-click on downloaded exe file. But read notices and warnings below
>
- [bt2qbt](#bt2qbt)
- [Feature](#user-content-feature)

View File

@ -15,11 +15,6 @@ func (transfer *TransferStructure) HandleStructures() {
// 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
@ -46,6 +41,6 @@ func (transfer *TransferStructure) HandleStructures() {
transfer.NumPieces = int64(len(transfer.TorrentFile.Info.Pieces)) / 20
transfer.HandleCompleted() // important handle priorities before handling pieces
transfer.HandleSavePaths()
transfer.HandleSavePaths() // and there we handle torrent name also
transfer.HandlePieces()
}

View File

@ -5,7 +5,6 @@ import (
"errors"
"fmt"
"github.com/rumanzo/bt2qbt/internal/options"
"io/ioutil"
"os"
)
@ -14,7 +13,7 @@ func ProcessLabels(opts *options.Opts, newtags []string) error {
// check if categories is new file. If it exists it must be unmarshaled. Default categories file contains only {}
var categoriesIsNew bool
file, err := os.OpenFile(opts.Categories, os.O_RDWR, 0644)
_, err := os.Stat(opts.Categories)
if errors.Is(err, os.ErrNotExist) {
categoriesIsNew = true
} else if err != nil {
@ -22,16 +21,11 @@ func ProcessLabels(opts *options.Opts, newtags []string) error {
}
if !categoriesIsNew {
dataRaw, err := ioutil.ReadAll(file)
dataRaw, err := os.ReadFile(opts.Categories)
if err != nil {
return errors.New(fmt.Sprintf("Unexpected error while read categories.json. Error:\n%v\n", err))
}
err = file.Close()
if err != nil {
return errors.New(fmt.Sprintf("Can't close categories.json. Error:\n%v\n", err))
}
err = json.Unmarshal(dataRaw, &categories)
if err != nil {
return errors.New(fmt.Sprintf("Unexpected error while unmarshaling categories.json. Error:\n%v\n", err))
@ -56,7 +50,7 @@ func ProcessLabels(opts *options.Opts, newtags []string) error {
return errors.New(fmt.Sprintf("Can't marshal categories. Error:\n%v\n", err))
}
err = ioutil.WriteFile(opts.Categories, newCategories, 0644)
err = os.WriteFile(opts.Categories, newCategories, 0644)
if err != nil {
return errors.New(fmt.Sprintf("Can't write categories.json. Error:\n%v\n", err))
}

View File

@ -2,13 +2,12 @@ package transfer
import (
"github.com/rumanzo/bt2qbt/internal/options"
"io/ioutil"
"os"
"testing"
)
func TestProcessLabelsExisting(t *testing.T) {
err := ioutil.WriteFile("../../test/categories_existing.json", []byte("{}"), 0755)
err := os.WriteFile("../../test/categories_existing.json", []byte("{}"), 0755)
if err != nil {
t.Fatalf("Can't write empty categories test file. Err: %v", err.Error())
}

View File

@ -149,7 +149,7 @@ func HandleTorrentFilePath(transferStructure *TransferStructure, key string) {
}
}
// if we can find torrent file, we start check another locations from options search paths
// FindTorrentFile if we can find torrent file, we start check another locations from options search paths
func FindTorrentFile(transferStructure *TransferStructure) error {
if _, err := os.Stat(transferStructure.TorrentFilePath); os.IsNotExist(err) {
for _, searchPath := range transferStructure.Opts.SearchPaths {

View File

@ -7,6 +7,7 @@ import (
"github.com/rumanzo/bt2qbt/internal/replace"
"github.com/rumanzo/bt2qbt/pkg/fileHelpers"
"github.com/rumanzo/bt2qbt/pkg/helpers"
"github.com/rumanzo/bt2qbt/pkg/normalization"
"github.com/rumanzo/bt2qbt/pkg/qBittorrentStructures"
"github.com/rumanzo/bt2qbt/pkg/torrentStructures"
"github.com/rumanzo/bt2qbt/pkg/utorrentStructs"
@ -15,7 +16,6 @@ import (
"regexp"
"strings"
"time"
"unicode/utf8"
)
//goland:noinspection GoNameStartsWithPackageName
@ -82,13 +82,13 @@ func (transfer *TransferStructure) HandleCaption() {
}
// HandleState transfer torrents state.
// if torrent has several files and it doesn't complete downloaded (priority), it will be stopped
// if torrent has several files, and it doesn't complete downloaded (priority), it will be stopped
func (transfer *TransferStructure) HandleState() {
if transfer.ResumeItem.Started == 0 {
transfer.Fastresume.Paused = 1
transfer.Fastresume.AutoManaged = 0
} else {
if len(transfer.TorrentFile.GetFileList()) > 1 {
if !transfer.TorrentFile.IsSingle() {
var parted bool
for _, prio := range transfer.Fastresume.FilePriority {
if prio == 0 {
@ -202,7 +202,7 @@ func (transfer *TransferStructure) HandlePieces() {
if transfer.Fastresume.Unfinished != nil {
transfer.FillWholePieces(0)
} else {
if len(transfer.TorrentFile.GetFileList()) > 0 {
if !transfer.TorrentFile.IsSingle() {
transfer.FillPiecesParted()
} else {
transfer.FillWholePieces(1)
@ -227,7 +227,8 @@ func (transfer *TransferStructure) FillPiecesParted() {
}
var fileOffsets []Offset
bytesLength := int64(0)
for _, file := range transfer.TorrentFile.GetFileListWB() { // need to adapt for torrents v2 version
fileList, _ := transfer.TorrentFile.GetFileListWB()
for _, file := range fileList {
fileFirstOffset := bytesLength + 1
bytesLength += file.Length
fileLastOffset := bytesLength
@ -267,31 +268,20 @@ func (transfer *TransferStructure) HandleSavePaths() {
// Original paths always ending with pathSeparator
// SubFolder or NoSubfolder never have ending pathSeparator
// qBtSavePath always has separator /, otherwise SavePath use os pathSeparator
if transfer.Magnet {
transfer.Fastresume.QBtContentLayout = "Original"
transfer.Fastresume.QbtSavePath = fileHelpers.Normalize(helpers.HandleCesu8(transfer.ResumeItem.Path), "/")
} else {
var torrentName string
var torrentNameOriginal string
if transfer.TorrentFile.Info.NameUTF8 != "" {
torrentName = helpers.HandleCesu8(transfer.TorrentFile.Info.NameUTF8)
torrentNameOriginal = transfer.TorrentFile.Info.NameUTF8
} else {
torrentName = helpers.HandleCesu8(transfer.TorrentFile.Info.Name)
torrentNameOriginal = transfer.TorrentFile.Info.Name
}
lastPathName := fileHelpers.Base(helpers.HandleCesu8(transfer.ResumeItem.Path))
var nameNormalized bool
transfer.Fastresume.Name, nameNormalized = normalization.FullNormalize(transfer.TorrentFile.GetTorrentName())
if len(transfer.TorrentFile.GetFileList()) > 0 {
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 {
lastPathName := fileHelpers.Base(helpers.HandleCesu8(transfer.ResumeItem.Path))
// if FileList contain only 1 file that means it is single file torrent
if !transfer.TorrentFile.IsSingle() {
fileList, filesNormalized := transfer.TorrentFile.GetFileList()
if lastPathName == transfer.Fastresume.Name && !filesNormalized && !nameNormalized {
transfer.Fastresume.QBtContentLayout = "Original"
transfer.Fastresume.QbtSavePath = fileHelpers.CutLastPath(helpers.HandleCesu8(transfer.ResumeItem.Path), transfer.Opts.PathSeparator)
if maxIndex := transfer.FindHighestIndexOfMappedFiles(); maxIndex >= 0 {
@ -309,7 +299,7 @@ func (transfer *TransferStructure) HandleSavePaths() {
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.MappedFiles[index] = fileHelpers.Join(append([]string{transfer.Fastresume.Name}, pathParts...), transfer.Opts.PathSeparator)
}
}
}
@ -319,10 +309,10 @@ func (transfer *TransferStructure) HandleSavePaths() {
}
} else {
transfer.Fastresume.QBtContentLayout = "NoSubfolder"
// 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(helpers.HandleCesu8(filePath), transfer.Opts.PathSeparator))
// NoSubfolder always has full mapped files, so we append all of them
for _, filePath := range fileList {
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 {
@ -344,7 +334,7 @@ func (transfer *TransferStructure) HandleSavePaths() {
}
} else {
transfer.Fastresume.QBtContentLayout = "Original" // utorrent\bittorrent don't support create subfolders for torrents with single file
if lastPathName != torrentNameOriginal {
if nameNormalized || lastPathName != transfer.Fastresume.Name {
//it means that we have renamed path and targets item, and should have mapped files
transfer.Fastresume.MappedFiles = []string{lastPathName}
}

View File

@ -39,6 +39,7 @@ func TestTransferStructure_HandleSavePaths(t *testing.T) {
QbtSavePath: `D:/torrents/`,
SavePath: `D:\torrents\`,
QBtContentLayout: "Original",
Name: `test_torrent.txt`,
},
},
},
@ -59,6 +60,7 @@ func TestTransferStructure_HandleSavePaths(t *testing.T) {
QbtSavePath: `E:/newfolder/`,
SavePath: `E:\newfolder\`,
QBtContentLayout: "Original",
Name: `test_torrent.txt`,
},
},
},
@ -79,6 +81,7 @@ func TestTransferStructure_HandleSavePaths(t *testing.T) {
QbtSavePath: `D:/torrents/`,
SavePath: `D:\torrents\`,
QBtContentLayout: "Original",
Name: `test_torrent.txt`,
},
},
},
@ -107,6 +110,7 @@ func TestTransferStructure_HandleSavePaths(t *testing.T) {
QbtSavePath: `D:/torrents/`,
SavePath: `D:\torrents\`,
QBtContentLayout: "Original",
Name: `test_torrent.txt`,
MappedFiles: []string{`renamed_test_torrent.txt`},
},
},
@ -135,6 +139,7 @@ func TestTransferStructure_HandleSavePaths(t *testing.T) {
Fastresume: &qBittorrentStructures.QBittorrentFastresume{
QbtSavePath: `/mnt/d/torrents/`,
SavePath: `/mnt/d/torrents/`,
Name: `test_torrent.txt`,
QBtContentLayout: "Original",
MappedFiles: []string{`renamed_test_torrent.txt`},
},
@ -162,6 +167,7 @@ func TestTransferStructure_HandleSavePaths(t *testing.T) {
QbtSavePath: `D:/torrents/`,
SavePath: `D:\torrents\`,
QBtContentLayout: "Original",
Name: `test_torrent`,
},
},
},
@ -187,6 +193,7 @@ func TestTransferStructure_HandleSavePaths(t *testing.T) {
QbtSavePath: `E:/newfolder/`,
SavePath: `E:\newfolder\`,
QBtContentLayout: "Original",
Name: `test_torrent`,
},
},
},
@ -212,6 +219,7 @@ func TestTransferStructure_HandleSavePaths(t *testing.T) {
QbtSavePath: `D:/torrents/`,
SavePath: `D:\torrents\`,
QBtContentLayout: "Original",
Name: `test_torrent`,
},
},
},
@ -245,6 +253,7 @@ func TestTransferStructure_HandleSavePaths(t *testing.T) {
QbtSavePath: `D:/torrents/`,
SavePath: `D:\torrents\`,
QBtContentLayout: "Original",
Name: `test_torrent`,
MappedFiles: []string{
``,
``,
@ -256,7 +265,7 @@ func TestTransferStructure_HandleSavePaths(t *testing.T) {
{
name: "010 Test torrent with windows folder (original) path with replace to linux paths and linux sep. Renamed File",
newTransferStructure: &TransferStructure{
Fastresume: &qBittorrentStructures.QBittorrentFastresume{},
Fastresume: &qBittorrentStructures.QBittorrentFastresume{Name: `test_torrent`},
ResumeItem: &utorrentStructs.ResumeItem{
Path: `D:\torrents\test_torrent`,
Targets: [][]interface{}{
@ -283,6 +292,7 @@ func TestTransferStructure_HandleSavePaths(t *testing.T) {
QbtSavePath: `/mnt/d/torrents/`,
SavePath: `/mnt/d/torrents/`,
QBtContentLayout: "Original",
Name: `test_torrent`,
MappedFiles: []string{
``,
``,
@ -294,7 +304,7 @@ func TestTransferStructure_HandleSavePaths(t *testing.T) {
{
name: "011 Test torrent with windows folder (NoSubfolder) path without replaces",
newTransferStructure: &TransferStructure{
Fastresume: &qBittorrentStructures.QBittorrentFastresume{},
Fastresume: &qBittorrentStructures.QBittorrentFastresume{Name: `test_torrent`},
ResumeItem: &utorrentStructs.ResumeItem{Path: `D:\torrents\test`},
TorrentFile: &torrentStructures.Torrent{
Info: &torrentStructures.TorrentInfo{
@ -312,6 +322,7 @@ func TestTransferStructure_HandleSavePaths(t *testing.T) {
Fastresume: &qBittorrentStructures.QBittorrentFastresume{
QbtSavePath: `D:/torrents/test`,
SavePath: `D:\torrents\test`,
Name: `test_torrent`,
MappedFiles: []string{
`dir1\file1.txt`,
`dir2\file2.txt`,
@ -324,7 +335,7 @@ func TestTransferStructure_HandleSavePaths(t *testing.T) {
{
name: "012 Test torrent with windows folder (NoSubfolder) path with replace",
newTransferStructure: &TransferStructure{
Fastresume: &qBittorrentStructures.QBittorrentFastresume{},
Fastresume: &qBittorrentStructures.QBittorrentFastresume{Name: `test_torrent`},
ResumeItem: &utorrentStructs.ResumeItem{Path: `D:\torrents\test`},
TorrentFile: &torrentStructures.Torrent{
Info: &torrentStructures.TorrentInfo{
@ -342,6 +353,7 @@ func TestTransferStructure_HandleSavePaths(t *testing.T) {
Fastresume: &qBittorrentStructures.QBittorrentFastresume{
QbtSavePath: `E:/newfolder/test`,
SavePath: `E:\newfolder\test`,
Name: `test_torrent`,
MappedFiles: []string{
`dir1\file1.txt`,
`dir2\file2.txt`,
@ -354,7 +366,7 @@ func TestTransferStructure_HandleSavePaths(t *testing.T) {
{
name: "013 Test torrent with windows folder (NoSubfolder) path without replaces. NameUTF8",
newTransferStructure: &TransferStructure{
Fastresume: &qBittorrentStructures.QBittorrentFastresume{},
Fastresume: &qBittorrentStructures.QBittorrentFastresume{Name: `test_torrent`},
ResumeItem: &utorrentStructs.ResumeItem{Path: `D:\torrents\test`},
TorrentFile: &torrentStructures.Torrent{
Info: &torrentStructures.TorrentInfo{
@ -372,6 +384,7 @@ func TestTransferStructure_HandleSavePaths(t *testing.T) {
Fastresume: &qBittorrentStructures.QBittorrentFastresume{
QbtSavePath: `D:/torrents/test`,
SavePath: `D:\torrents\test`,
Name: `test_torrent`,
MappedFiles: []string{
`dir1\file1.txt`,
`dir2\file2.txt`,
@ -384,7 +397,7 @@ func TestTransferStructure_HandleSavePaths(t *testing.T) {
{
name: "014 Test torrent with windows folder (NoSubfolder) path without replaces. Renamed File",
newTransferStructure: &TransferStructure{
Fastresume: &qBittorrentStructures.QBittorrentFastresume{},
Fastresume: &qBittorrentStructures.QBittorrentFastresume{Name: `test_torrent`},
ResumeItem: &utorrentStructs.ResumeItem{
Path: `D:\torrents\test`,
Targets: [][]interface{}{
@ -411,6 +424,7 @@ func TestTransferStructure_HandleSavePaths(t *testing.T) {
QbtSavePath: `D:/torrents/test`,
SavePath: `D:\torrents\test`,
QBtContentLayout: "NoSubfolder",
Name: `test_torrent`,
MappedFiles: []string{
`dir1\file1.txt`,
`dir2\file2.txt`,
@ -422,7 +436,7 @@ func TestTransferStructure_HandleSavePaths(t *testing.T) {
{
name: "015 Test torrent with windows folder (NoSubfolder) path with replace to linux paths and linux sep. Renamed File",
newTransferStructure: &TransferStructure{
Fastresume: &qBittorrentStructures.QBittorrentFastresume{},
Fastresume: &qBittorrentStructures.QBittorrentFastresume{Name: `test_torrent`},
ResumeItem: &utorrentStructs.ResumeItem{
Path: `D:\torrents\test`,
Targets: [][]interface{}{
@ -448,6 +462,7 @@ func TestTransferStructure_HandleSavePaths(t *testing.T) {
Fastresume: &qBittorrentStructures.QBittorrentFastresume{
QbtSavePath: `/mnt/d/torrents/test`,
SavePath: `/mnt/d/torrents/test`,
Name: `test_torrent`,
QBtContentLayout: "NoSubfolder",
MappedFiles: []string{
`dir1/file1.txt`,
@ -478,6 +493,7 @@ func TestTransferStructure_HandleSavePaths(t *testing.T) {
Fastresume: &qBittorrentStructures.QBittorrentFastresume{
QbtSavePath: `D:/torrents/test`,
SavePath: `D:\torrents\test`,
Name: `test_torrent`,
MappedFiles: []string{
`dir1\file1.txt`,
`dir2\file2.txt`,
@ -491,7 +507,7 @@ func TestTransferStructure_HandleSavePaths(t *testing.T) {
name: "017 Test torrent with windows single nofolder (original) path without replaces",
mustFail: true,
newTransferStructure: &TransferStructure{
Fastresume: &qBittorrentStructures.QBittorrentFastresume{},
Fastresume: &qBittorrentStructures.QBittorrentFastresume{Name: `test_torrent.txt`},
ResumeItem: &utorrentStructs.ResumeItem{Path: `D:\torrents\test_torrent.txt`},
TorrentFile: &torrentStructures.Torrent{
Info: &torrentStructures.TorrentInfo{
@ -504,6 +520,7 @@ func TestTransferStructure_HandleSavePaths(t *testing.T) {
Fastresume: &qBittorrentStructures.QBittorrentFastresume{
QbtSavePath: `D:/torrents/`,
SavePath: `D:\torre`,
Name: `test_torrent.txt`,
QBtContentLayout: "Original",
},
},
@ -511,7 +528,7 @@ func TestTransferStructure_HandleSavePaths(t *testing.T) {
{
name: "018 Test torrent with windows folder (original) path without replaces. Moved files with absolute paths",
newTransferStructure: &TransferStructure{
Fastresume: &qBittorrentStructures.QBittorrentFastresume{},
Fastresume: &qBittorrentStructures.QBittorrentFastresume{Name: `test_torrent`},
ResumeItem: &utorrentStructs.ResumeItem{
Path: `D:\torrents\test_torrent`,
Targets: [][]interface{}{
@ -547,6 +564,7 @@ func TestTransferStructure_HandleSavePaths(t *testing.T) {
Fastresume: &qBittorrentStructures.QBittorrentFastresume{
QbtSavePath: `D:/torrents/`,
SavePath: `D:\torrents\`,
Name: `test_torrent`,
QBtContentLayout: "Original",
MappedFiles: []string{
``,
@ -561,7 +579,7 @@ func TestTransferStructure_HandleSavePaths(t *testing.T) {
{
name: "019 Test torrent with windows folder (original) path with replaces. Moved files with absolute paths",
newTransferStructure: &TransferStructure{
Fastresume: &qBittorrentStructures.QBittorrentFastresume{},
Fastresume: &qBittorrentStructures.QBittorrentFastresume{Name: `test_torrent`},
ResumeItem: &utorrentStructs.ResumeItem{
Path: `D:\torrents\test_torrent`,
Targets: [][]interface{}{
@ -597,6 +615,7 @@ func TestTransferStructure_HandleSavePaths(t *testing.T) {
Fastresume: &qBittorrentStructures.QBittorrentFastresume{
QbtSavePath: `/mnt/d/torrents/`,
SavePath: `/mnt/d/torrents/`,
Name: `test_torrent`,
QBtContentLayout: "Original",
MappedFiles: []string{
``,
@ -611,7 +630,7 @@ func TestTransferStructure_HandleSavePaths(t *testing.T) {
{
name: "020 Test torrent with windows folder (NoSubfolder) path without replaces. Moved files with absolute paths",
newTransferStructure: &TransferStructure{
Fastresume: &qBittorrentStructures.QBittorrentFastresume{},
Fastresume: &qBittorrentStructures.QBittorrentFastresume{Name: `test_torrent`},
ResumeItem: &utorrentStructs.ResumeItem{
Path: `D:\torrents\test`,
Targets: [][]interface{}{
@ -647,6 +666,7 @@ func TestTransferStructure_HandleSavePaths(t *testing.T) {
Fastresume: &qBittorrentStructures.QBittorrentFastresume{
QbtSavePath: `D:/torrents/test`,
SavePath: `D:\torrents\test`,
Name: `test_torrent`,
QBtContentLayout: "NoSubfolder",
MappedFiles: []string{
`dir1\file1.txt`,
@ -661,7 +681,7 @@ func TestTransferStructure_HandleSavePaths(t *testing.T) {
{
name: "021 Test torrent with windows share folder (Original) path without replaces. Moved files with absolute paths. Windows share",
newTransferStructure: &TransferStructure{
Fastresume: &qBittorrentStructures.QBittorrentFastresume{},
Fastresume: &qBittorrentStructures.QBittorrentFastresume{Name: `test_torrent`},
ResumeItem: &utorrentStructs.ResumeItem{
Path: `\\torrents\test_torrent`,
Targets: [][]interface{}{
@ -697,6 +717,7 @@ func TestTransferStructure_HandleSavePaths(t *testing.T) {
Fastresume: &qBittorrentStructures.QBittorrentFastresume{
QbtSavePath: `//torrents/`,
SavePath: `\\torrents\`,
Name: `test_torrent`,
QBtContentLayout: "Original",
MappedFiles: []string{
``,
@ -711,7 +732,7 @@ func TestTransferStructure_HandleSavePaths(t *testing.T) {
{
name: "022 Test torrent with windows share folder (NoSubfolder) path without replaces. Moved files with absolute paths",
newTransferStructure: &TransferStructure{
Fastresume: &qBittorrentStructures.QBittorrentFastresume{},
Fastresume: &qBittorrentStructures.QBittorrentFastresume{Name: `test_torrent`},
ResumeItem: &utorrentStructs.ResumeItem{
Path: `\\torrents\test`,
Targets: [][]interface{}{
@ -747,6 +768,7 @@ func TestTransferStructure_HandleSavePaths(t *testing.T) {
Fastresume: &qBittorrentStructures.QBittorrentFastresume{
QbtSavePath: `//torrents/test`,
SavePath: `\\torrents\test`,
Name: `test_torrent`,
QBtContentLayout: "NoSubfolder",
MappedFiles: []string{
`dir1\file1.txt`,
@ -761,7 +783,7 @@ func TestTransferStructure_HandleSavePaths(t *testing.T) {
{
name: "023 Test torrent with windows folder (Original) path without replaces. Absolute paths. Windows share Replace",
newTransferStructure: &TransferStructure{
Fastresume: &qBittorrentStructures.QBittorrentFastresume{},
Fastresume: &qBittorrentStructures.QBittorrentFastresume{Name: `test_torrent`},
ResumeItem: &utorrentStructs.ResumeItem{
Path: `\\torrents\test_torrent`,
Targets: [][]interface{}{
@ -797,6 +819,7 @@ func TestTransferStructure_HandleSavePaths(t *testing.T) {
Fastresume: &qBittorrentStructures.QBittorrentFastresume{
QbtSavePath: `//torrents/`,
SavePath: `//torrents/`,
Name: `test_torrent`,
QBtContentLayout: "Original",
MappedFiles: []string{
``,
@ -847,6 +870,7 @@ func TestTransferStructure_HandleSavePaths(t *testing.T) {
Fastresume: &qBittorrentStructures.QBittorrentFastresume{
QbtSavePath: `//torrents/test`,
SavePath: `//torrents/test`,
Name: `test_torrent`,
QBtContentLayout: "NoSubfolder",
MappedFiles: []string{
`dir1/file1.txt`,
@ -866,7 +890,7 @@ func TestTransferStructure_HandleSavePaths(t *testing.T) {
Path: `D:\torrents\test`,
},
TorrentFile: &torrentStructures.Torrent{
Info: &torrentStructures.TorrentInfo{},
Info: &torrentStructures.TorrentInfo{Name: "torrentname"},
},
Opts: &options.Opts{PathSeparator: `\`},
Magnet: true,
@ -875,6 +899,7 @@ func TestTransferStructure_HandleSavePaths(t *testing.T) {
Fastresume: &qBittorrentStructures.QBittorrentFastresume{
QbtSavePath: `D:/torrents/test`,
SavePath: `D:\torrents\test`,
Name: "torrentname",
QBtContentLayout: "Original",
},
},
@ -897,6 +922,7 @@ func TestTransferStructure_HandleSavePaths(t *testing.T) {
Fastresume: &qBittorrentStructures.QBittorrentFastresume{
QbtSavePath: `D:/`,
SavePath: `D:\`,
Name: `test.txt`,
QBtContentLayout: "Original",
},
},
@ -926,6 +952,7 @@ func TestTransferStructure_HandleSavePaths(t *testing.T) {
Fastresume: &qBittorrentStructures.QBittorrentFastresume{
QbtSavePath: `D:/`,
SavePath: `D:\`,
Name: `test_torrent`,
QBtContentLayout: "Original",
},
},
@ -967,6 +994,7 @@ func TestTransferStructure_HandleSavePaths(t *testing.T) {
Fastresume: &qBittorrentStructures.QBittorrentFastresume{
QbtSavePath: `D:/torrents/`,
SavePath: `D:\torrents\`,
Name: "test_torrent \xf0\x9f\x86\x95",
QBtContentLayout: "Original",
MappedFiles: []string{
"E:\\somedir1 \xf0\x9f\x86\x95\\\xf0\x9f\x86\x95 renamed_test_torrent2.txt",
@ -997,6 +1025,7 @@ func TestTransferStructure_HandleSavePaths(t *testing.T) {
Fastresume: &qBittorrentStructures.QBittorrentFastresume{
QbtSavePath: "D:/torrents/renamed test_torrent \xf0\x9f\x86\x95",
SavePath: "D:\\torrents\\renamed test_torrent \xf0\x9f\x86\x95",
Name: "test_torrent \xf0\x9f\x86\x95",
QBtContentLayout: "NoSubfolder",
MappedFiles: []string{
"dir1\\\xf0\x9f\x86\x95 file1.txt",
@ -1043,6 +1072,7 @@ func TestTransferStructure_HandleSavePaths(t *testing.T) {
Fastresume: &qBittorrentStructures.QBittorrentFastresume{
QbtSavePath: "D:/torrents/test_torrent \xf0\x9f\x86\x95",
SavePath: "D:\\torrents\\test_torrent \xf0\x9f\x86\x95",
Name: "test_torrent \xf0\x9f\x86\x95",
QBtContentLayout: "NoSubfolder",
MappedFiles: []string{
"E:\\somedir1 \xf0\x9f\x86\x95\\\xf0\x9f\x86\x95 renamed_test_torrent2.txt",
@ -1073,6 +1103,7 @@ func TestTransferStructure_HandleSavePaths(t *testing.T) {
Fastresume: &qBittorrentStructures.QBittorrentFastresume{
QbtSavePath: "D:/torrents/renamed test_torrent \xf0\x9f\x86\x95",
SavePath: "D:\\torrents\\renamed test_torrent \xf0\x9f\x86\x95",
Name: "test_torrent \xf0\x9f\x86\x95",
QBtContentLayout: "NoSubfolder",
MappedFiles: []string{
"dir1\\\xf0\x9f\x86\x95 file1.txt",
@ -1105,6 +1136,7 @@ func TestTransferStructure_HandleSavePaths(t *testing.T) {
Fastresume: &qBittorrentStructures.QBittorrentFastresume{
QbtSavePath: "D:/torrents/test_torrent",
SavePath: "D:\\torrents\\test_torrent",
Name: "test_torrent",
QBtContentLayout: "NoSubfolder",
MappedFiles: []string{
"dir1\\\xf0\x9f\x86\x95 file1.txt",
@ -1156,6 +1188,7 @@ func TestTransferStructure_HandleSavePaths(t *testing.T) {
Fastresume: &qBittorrentStructures.QBittorrentFastresume{
QbtSavePath: "D:/torrents/test_torrent",
SavePath: "D:\\torrents\\test_torrent",
Name: "test_torrent",
QBtContentLayout: "NoSubfolder",
MappedFiles: []string{
"E:\\somedir1 \xf0\x9f\x86\x95\\\xf0\x9f\x86\x95 renamed_test_torrent2.txt",
@ -1184,6 +1217,7 @@ func TestTransferStructure_HandleSavePaths(t *testing.T) {
Fastresume: &qBittorrentStructures.QBittorrentFastresume{
QbtSavePath: `D:/torrents/`,
SavePath: `D:\torrents\`,
Name: "test_torrent \xf0\x9f\x86\x95.txt",
MappedFiles: []string{
"test_torrent \xf0\x9f\x86\x95.txt",
},
@ -1211,6 +1245,7 @@ func TestTransferStructure_HandleSavePaths(t *testing.T) {
Fastresume: &qBittorrentStructures.QBittorrentFastresume{
QbtSavePath: `D:/torrents/renamed test_torrent`,
SavePath: `D:\torrents\renamed test_torrent`,
Name: `test_torrent`,
QBtContentLayout: "NoSubfolder",
MappedFiles: []string{
`#test _ test [01]{1} [6K].jpg`,
@ -1220,6 +1255,435 @@ func TestTransferStructure_HandleSavePaths(t *testing.T) {
Opts: &options.Opts{PathSeparator: `\`},
},
},
{
name: "035 Test torrent with windows folder (NoSubfolder) with prohibited symbols.",
newTransferStructure: &TransferStructure{
Fastresume: &qBittorrentStructures.QBittorrentFastresume{},
ResumeItem: &utorrentStructs.ResumeItem{Path: `D:\torrents\renamed test_torrent`},
TorrentFile: &torrentStructures.Torrent{
Info: &torrentStructures.TorrentInfo{
Name: "test_torrent",
Files: []*torrentStructures.TorrentFile{
&torrentStructures.TorrentFile{Path: []string{`#test | test [01]{1} [6K].jpg`}},
&torrentStructures.TorrentFile{Path: []string{`testdir1 collection`, `testdir2?`, `1.jpg`}},
},
},
},
Opts: &options.Opts{PathSeparator: `\`},
},
expected: &TransferStructure{
Fastresume: &qBittorrentStructures.QBittorrentFastresume{
QbtSavePath: `D:/torrents/renamed test_torrent`,
SavePath: `D:\torrents\renamed test_torrent`,
Name: `test_torrent`,
QBtContentLayout: "NoSubfolder",
MappedFiles: []string{
`#test _ test [01]{1} [6K].jpg`,
`testdir1 collection\testdir2_\1.jpg`,
},
},
Opts: &options.Opts{PathSeparator: `\`},
},
},
{
name: "036 Test torrent with windows single nofolder (original) path without replaces. With prohibited symbols",
newTransferStructure: &TransferStructure{
Fastresume: &qBittorrentStructures.QBittorrentFastresume{},
ResumeItem: &utorrentStructs.ResumeItem{Path: `D:\torrents\test_torrent.txt`},
TorrentFile: &torrentStructures.Torrent{
Info: &torrentStructures.TorrentInfo{
Name: `test|torrent.txt`,
},
},
Opts: &options.Opts{PathSeparator: `\`},
},
expected: &TransferStructure{
Fastresume: &qBittorrentStructures.QBittorrentFastresume{
Name: `test_torrent.txt`,
QbtSavePath: `D:/torrents/`,
SavePath: `D:\torrents\`,
QBtContentLayout: "Original",
MappedFiles: []string{
`test_torrent.txt`,
},
},
},
},
{
name: "037 Test torrent with windows folder (NoSubfolder) with prohibited symbols. *nix path separator",
newTransferStructure: &TransferStructure{
Fastresume: &qBittorrentStructures.QBittorrentFastresume{},
ResumeItem: &utorrentStructs.ResumeItem{Path: `D:\torrents\renamed test_torrent`},
TorrentFile: &torrentStructures.Torrent{
Info: &torrentStructures.TorrentInfo{
Name: "test_torrent",
Files: []*torrentStructures.TorrentFile{
&torrentStructures.TorrentFile{Path: []string{`#test | test [01]{1} [6K].jpg`}},
&torrentStructures.TorrentFile{Path: []string{`testdir1 collection`, `testdir2?`, `1.jpg`}},
},
},
},
Opts: &options.Opts{PathSeparator: `/`},
},
expected: &TransferStructure{
Fastresume: &qBittorrentStructures.QBittorrentFastresume{
QbtSavePath: `D:/torrents/renamed test_torrent`,
SavePath: `D:/torrents/renamed test_torrent`,
Name: `test_torrent`,
QBtContentLayout: "NoSubfolder",
MappedFiles: []string{
`#test _ test [01]{1} [6K].jpg`,
`testdir1 collection/testdir2_/1.jpg`,
},
},
},
},
{
name: "038 Test torrent with windows single nofolder (original) path without replaces. With prohibited symbols. *nix path separator",
newTransferStructure: &TransferStructure{
Fastresume: &qBittorrentStructures.QBittorrentFastresume{},
ResumeItem: &utorrentStructs.ResumeItem{Path: `D:\torrents\test_torrent.txt`},
TorrentFile: &torrentStructures.Torrent{
Info: &torrentStructures.TorrentInfo{
Name: `test|torrent.txt`,
},
},
Opts: &options.Opts{PathSeparator: `/`},
},
expected: &TransferStructure{
Fastresume: &qBittorrentStructures.QBittorrentFastresume{
QbtSavePath: `D:/torrents/`,
SavePath: `D:/torrents/`,
Name: `test_torrent.txt`,
QBtContentLayout: "Original",
MappedFiles: []string{
`test_torrent.txt`,
},
},
},
},
{
name: "039 Test torrent with windows single nofolder (original) path with renamed file. v2 torrent",
newTransferStructure: &TransferStructure{
Fastresume: &qBittorrentStructures.QBittorrentFastresume{},
ResumeItem: &utorrentStructs.ResumeItem{
Path: `D:\torrents\testtorrent.txt`,
Targets: [][]interface{}{
[]interface{}{
int64(0),
"testtorrent.txt",
},
},
},
TorrentFile: &torrentStructures.Torrent{
Info: &torrentStructures.TorrentInfo{
Name: `testtorrent.txt`,
FileTree: map[string]interface{}{
`testtorrent.txt`: map[string]interface{}{
``: map[string]interface{}{
`length`: int64(100),
`pieces_root`: []byte{},
},
},
},
},
},
Opts: &options.Opts{PathSeparator: `\`},
},
expected: &TransferStructure{
Fastresume: &qBittorrentStructures.QBittorrentFastresume{
QbtSavePath: `D:/torrents/`,
SavePath: `D:\torrents\`,
Name: `testtorrent.txt`,
QBtContentLayout: "Original",
},
},
},
{
name: "040 Test torrent with windows folder (original) path with renamed file. v2 torrent",
newTransferStructure: &TransferStructure{
Fastresume: &qBittorrentStructures.QBittorrentFastresume{},
ResumeItem: &utorrentStructs.ResumeItem{
Path: `D:\torrents\testtorrent`,
Targets: [][]interface{}{
[]interface{}{
int64(1),
"testtorrent2.txt",
},
},
},
TorrentFile: &torrentStructures.Torrent{
Info: &torrentStructures.TorrentInfo{
Name: `testtorrent`,
FileTree: map[string]interface{}{
`testtorrent1.txt`: map[string]interface{}{
``: map[string]interface{}{
`length`: int64(100),
`pieces_root`: []byte{},
},
},
`testtorrent3.txt`: map[string]interface{}{
``: map[string]interface{}{
`length`: int64(100),
`pieces_root`: []byte{},
},
},
},
},
},
Opts: &options.Opts{PathSeparator: `\`},
},
expected: &TransferStructure{
Fastresume: &qBittorrentStructures.QBittorrentFastresume{
QbtSavePath: `D:/torrents/`,
SavePath: `D:\torrents\`,
Name: `testtorrent`,
QBtContentLayout: "Original",
MappedFiles: []string{
``,
`testtorrent\testtorrent2.txt`,
},
},
},
},
{
name: "041 Test torrent with multi file torrent (NoSubfolder) with prohibited symbols in name",
newTransferStructure: &TransferStructure{
Fastresume: &qBittorrentStructures.QBittorrentFastresume{},
ResumeItem: &utorrentStructs.ResumeItem{
Path: `D:\test_torrent_test`,
},
TorrentFile: &torrentStructures.Torrent{
Info: &torrentStructures.TorrentInfo{
Name: "test_torrent/test",
Files: []*torrentStructures.TorrentFile{
&torrentStructures.TorrentFile{Path: []string{"dir1", "file1.txt"}},
&torrentStructures.TorrentFile{Path: []string{"dir2", "file2.txt"}},
&torrentStructures.TorrentFile{Path: []string{"file0.txt"}},
&torrentStructures.TorrentFile{Path: []string{"file1.txt"}},
&torrentStructures.TorrentFile{Path: []string{"file2.txt"}},
},
},
},
Opts: &options.Opts{PathSeparator: `\`},
},
expected: &TransferStructure{
Fastresume: &qBittorrentStructures.QBittorrentFastresume{
QbtSavePath: `D:/test_torrent_test`,
SavePath: `D:\test_torrent_test`,
Name: `test_torrent_test`,
QBtContentLayout: "NoSubfolder",
MappedFiles: []string{
`dir1\file1.txt`,
`dir2\file2.txt`,
`file0.txt`,
`file1.txt`,
`file2.txt`,
},
},
},
},
{
name: "042 Test torrent with multi file torrent with renames (NoSubFolder) with prohibited symbols in name",
newTransferStructure: &TransferStructure{
Fastresume: &qBittorrentStructures.QBittorrentFastresume{},
ResumeItem: &utorrentStructs.ResumeItem{
Path: `D:\test_torrent_test`,
Targets: [][]interface{}{
[]interface{}{
int64(2),
"file0_file.txt",
},
},
},
TorrentFile: &torrentStructures.Torrent{
Info: &torrentStructures.TorrentInfo{
Name: "test_torrent/test",
Files: []*torrentStructures.TorrentFile{
&torrentStructures.TorrentFile{Path: []string{"dir1", "file1.txt"}},
&torrentStructures.TorrentFile{Path: []string{"dir2", "file2.txt"}},
&torrentStructures.TorrentFile{Path: []string{"file0.txt"}},
&torrentStructures.TorrentFile{Path: []string{"file1.txt"}},
&torrentStructures.TorrentFile{Path: []string{"file2.txt"}},
},
},
},
Opts: &options.Opts{PathSeparator: `\`},
},
expected: &TransferStructure{
Fastresume: &qBittorrentStructures.QBittorrentFastresume{
QbtSavePath: `D:/test_torrent_test`,
SavePath: `D:\test_torrent_test`,
Name: `test_torrent_test`,
QBtContentLayout: "NoSubfolder",
MappedFiles: []string{
`dir1\file1.txt`,
`dir2\file2.txt`,
`file0_file.txt`,
`file1.txt`,
`file2.txt`,
},
},
},
},
{
name: "043 Test torrent with multi file torrent (NoSubfolder) with space at the end of torrent name",
newTransferStructure: &TransferStructure{
Fastresume: &qBittorrentStructures.QBittorrentFastresume{},
ResumeItem: &utorrentStructs.ResumeItem{
Path: `D:\test_torrent_`,
},
TorrentFile: &torrentStructures.Torrent{
Info: &torrentStructures.TorrentInfo{
Name: "test_torrent ",
Files: []*torrentStructures.TorrentFile{
&torrentStructures.TorrentFile{Path: []string{"dir1", "file1.txt"}},
&torrentStructures.TorrentFile{Path: []string{"dir2", "file2.txt"}},
&torrentStructures.TorrentFile{Path: []string{"file0.txt"}},
&torrentStructures.TorrentFile{Path: []string{"file1.txt"}},
&torrentStructures.TorrentFile{Path: []string{"file2.txt"}},
},
},
},
Opts: &options.Opts{PathSeparator: `\`},
},
expected: &TransferStructure{
Fastresume: &qBittorrentStructures.QBittorrentFastresume{
QbtSavePath: `D:/test_torrent_`,
SavePath: `D:\test_torrent_`,
Name: `test_torrent_`,
QBtContentLayout: "NoSubfolder",
MappedFiles: []string{
`dir1\file1.txt`,
`dir2\file2.txt`,
`file0.txt`,
`file1.txt`,
`file2.txt`,
},
},
},
},
{
name: "044 Test torrent with multi file torrent (NoSubfolder) with space at the end directory",
newTransferStructure: &TransferStructure{
Fastresume: &qBittorrentStructures.QBittorrentFastresume{},
ResumeItem: &utorrentStructs.ResumeItem{
Path: `D:\test_torrent_`,
},
TorrentFile: &torrentStructures.Torrent{
Info: &torrentStructures.TorrentInfo{
Name: "test_torrent ",
Files: []*torrentStructures.TorrentFile{
&torrentStructures.TorrentFile{Path: []string{"file0.txt"}},
&torrentStructures.TorrentFile{Path: []string{"file1.txt"}},
&torrentStructures.TorrentFile{Path: []string{"file2.txt"}},
&torrentStructures.TorrentFile{Path: []string{"dir1 ", "dir2 ", "file1.txt"}},
&torrentStructures.TorrentFile{Path: []string{"dir3 ", "file2.txt"}},
},
},
},
Opts: &options.Opts{PathSeparator: `\`},
},
expected: &TransferStructure{
Fastresume: &qBittorrentStructures.QBittorrentFastresume{
QbtSavePath: `D:/test_torrent_`,
SavePath: `D:\test_torrent_`,
Name: `test_torrent_`,
QBtContentLayout: "NoSubfolder",
MappedFiles: []string{
`file0.txt`,
`file1.txt`,
`file2.txt`,
`dir1_\dir2_\file1.txt`,
`dir3_\file2.txt`,
},
},
},
},
{
name: "045 Test torrent with multi file torrent (NoSubfolder) with space at the end directory with renamed files",
newTransferStructure: &TransferStructure{
Fastresume: &qBittorrentStructures.QBittorrentFastresume{},
ResumeItem: &utorrentStructs.ResumeItem{
Path: `D:\test_torrent_`,
Targets: [][]interface{}{
[]interface{}{
int64(2),
"file2_renamed.txt",
},
},
},
TorrentFile: &torrentStructures.Torrent{
Info: &torrentStructures.TorrentInfo{
Name: "test_torrent ",
Files: []*torrentStructures.TorrentFile{
&torrentStructures.TorrentFile{Path: []string{"file0.txt"}},
&torrentStructures.TorrentFile{Path: []string{"file1.txt"}},
&torrentStructures.TorrentFile{Path: []string{"file2.txt"}},
&torrentStructures.TorrentFile{Path: []string{"dir1 ", "dir2 ", "file1.txt"}},
&torrentStructures.TorrentFile{Path: []string{"dir3 ", "file2.txt"}},
},
},
},
Opts: &options.Opts{PathSeparator: `\`},
},
expected: &TransferStructure{
Fastresume: &qBittorrentStructures.QBittorrentFastresume{
QbtSavePath: `D:/test_torrent_`,
SavePath: `D:\test_torrent_`,
Name: `test_torrent_`,
QBtContentLayout: "NoSubfolder",
MappedFiles: []string{
`file0.txt`,
`file1.txt`,
`file2_renamed.txt`,
`dir1_\dir2_\file1.txt`,
`dir3_\file2.txt`,
},
},
},
},
{
name: "046 Test torrent with multi file torrent with transfer to NoSubfolder cesu8 symbols in names",
newTransferStructure: &TransferStructure{
Fastresume: &qBittorrentStructures.QBittorrentFastresume{},
ResumeItem: &utorrentStructs.ResumeItem{
Path: "D:\\test_slashes_emoji \xed\xa0\xbc\xed\xb6\x95_",
Caption: "test_slashes_emoji \xed\xa0\xbc\xed\xb6\x95_",
},
TorrentFile: &torrentStructures.Torrent{
Info: &torrentStructures.TorrentInfo{
Name: "test_slashes/emoji \xed\xa0\xbc\xed\xb6\x95 ",
Files: []*torrentStructures.TorrentFile{
&torrentStructures.TorrentFile{Path: []string{"file_with_emoji \xed\xa0\xbc\xed\xb6\x95.txt"}},
&torrentStructures.TorrentFile{Path: []string{"file_with/slash.txt"}},
&torrentStructures.TorrentFile{Path: []string{"testdir_with_emoji_and_space \xed\xa0\xbc\xed\xb6\x95 ", "file_with_emoji \xed\xa0\xbc\xed\xb6\x95.txt"}},
&torrentStructures.TorrentFile{Path: []string{"testdir_with_emoji_and_space \xed\xa0\xbc\xed\xb6\x95 ", "file_with/slash.txt"}},
&torrentStructures.TorrentFile{Path: []string{"testdir_with_space ", "file_with_emoji \xed\xa0\xbc\xed\xb6\x95.txt"}},
&torrentStructures.TorrentFile{Path: []string{"testdir_with_space ", "file_with/slash.txt"}},
},
},
},
Opts: &options.Opts{PathSeparator: `\`},
},
expected: &TransferStructure{
Fastresume: &qBittorrentStructures.QBittorrentFastresume{
QbtSavePath: "D:/test_slashes_emoji \xf0\x9f\x86\x95_",
SavePath: "D:\\test_slashes_emoji \xf0\x9f\x86\x95_",
Name: "test_slashes_emoji \xf0\x9f\x86\x95_",
QbtName: "test_slashes_emoji \xf0\x9f\x86\x95_",
QBtContentLayout: `NoSubfolder`,
MappedFiles: []string{
"file_with_emoji \xf0\x9f\x86\x95.txt",
"file_with_slash.txt",
"testdir_with_emoji_and_space \xf0\x9f\x86\x95_\\file_with_emoji \xf0\x9f\x86\x95.txt",
"testdir_with_emoji_and_space \xf0\x9f\x86\x95_\\file_with_slash.txt",
"testdir_with_space_\\file_with_emoji \xf0\x9f\x86\x95.txt",
"testdir_with_space_\\file_with_slash.txt",
},
},
},
},
}
for _, testCase := range cases {
t.Run(testCase.name, func(t *testing.T) {
@ -1228,6 +1692,8 @@ func TestTransferStructure_HandleSavePaths(t *testing.T) {
testCase.newTransferStructure.Replace = replaces
testCase.expected.Replace = replaces
}
testCase.newTransferStructure.Fastresume.Name, _ = testCase.newTransferStructure.TorrentFile.GetNormalizedTorrentName()
testCase.newTransferStructure.HandleCaption()
testCase.newTransferStructure.HandleSavePaths()
equal := reflect.DeepEqual(testCase.expected.Fastresume, testCase.newTransferStructure.Fastresume)
if !equal && !testCase.mustFail {
@ -1235,9 +1701,9 @@ 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:\nGot: %#v\nExpect %#v\nDiff: %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))
t.Fatalf("Unexpected error: structures are equal, but they shouldn't\nGot: %v\n", spew.Sdump(testCase.newTransferStructure.Fastresume))
}
})
}

View File

@ -129,7 +129,7 @@ func TestJoin(t *testing.T) {
expected: `..\..\testdir\my test file.txt`,
},
{
name: "009 windows relative paths join. check normalize",
name: "009 linux relative paths join. check normalize",
paths: []string{`./testdir/../testdir/../testdir/./`, `my test file.txt`},
separator: `/`,
expected: `testdir/my test file.txt`,

View File

@ -6,8 +6,8 @@ import (
"github.com/crazytyper/go-cesu8"
"github.com/zeebo/bencode"
"io"
"io/ioutil"
"os"
"regexp"
"strconv"
"strings"
)
@ -35,7 +35,7 @@ func CheckExists(s string, arr []string) (bool, string) {
}
func DecodeTorrentFile(path string, decodeTo interface{}) error {
dat, err := ioutil.ReadFile(path)
dat, err := os.ReadFile(path)
if err != nil {
return err
}
@ -119,3 +119,9 @@ func HandleCesu8(str string) string {
}
return str
}
// ReplaceAllSymbols Replace all symbols in set to replacer
func ReplaceAllSymbols(str string, set string, replacer string) string {
re := regexp.MustCompilePOSIX(`[` + regexp.QuoteMeta(set) + `]`)
return re.ReplaceAllString(str, replacer)
}

View File

@ -57,6 +57,7 @@ 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"
@ -64,3 +65,71 @@ func TestEmojiCesu8(t *testing.T) {
t.Fatalf("Cesu8 to utf-8 transformation fail")
}
}
func TestReplaceAllSymbols(t *testing.T) {
type Case struct {
name string
str string
set string
replacer string
expected string
}
cases := []Case{
{
name: "001 one symbol",
str: `qwerty`,
set: `qry`,
replacer: `_`,
expected: `_we_t_`,
},
{
name: "002 several replacer symbol",
str: `qwerty`,
set: `qry`,
replacer: `AAA`,
expected: `AAAweAAAtAAA`,
},
{
name: "003 several replacer symbol that exists in str",
str: `qwerty`,
set: `qry`,
replacer: `qwerty`,
expected: `qwertyweqwertytqwerty`,
},
{
name: "004 several replacer symbol that exists in str with special symbols",
str: `[qwerty]`,
set: `[qry]`,
replacer: `qwerty`,
expected: `qwertyqwertyweqwertytqwertyqwerty`,
},
{
name: "005 several replacer symbol that exists in str with special symbols",
str: `[qwerty]`,
set: `[qry]`,
replacer: `[qwerty]`,
expected: `[qwerty][qwerty]we[qwerty]t[qwerty][qwerty]`,
},
{
name: "006 emoji replace",
str: `qwer🚎y`,
set: `🚎`,
replacer: `_`,
expected: `qwer_y`,
},
{
name: "006 two emoji replace",
str: `qwer🚎y👍`,
set: `🚎😊`,
replacer: `_`,
expected: `qwer_y👍`,
},
}
for _, testCase := range cases {
t.Run(testCase.name, func(t *testing.T) {
replaced := ReplaceAllSymbols(testCase.str, testCase.set, testCase.replacer)
if replaced != testCase.expected {
t.Fatalf("Unexpected error:\nstr: %v set: %v replacer: %v\nGot: %v\nExpect %v\n", testCase.str, testCase.set, testCase.replacer, replaced, testCase.expected)
}
})
}
}

View File

@ -0,0 +1,35 @@
package normalization
import (
"github.com/rumanzo/bt2qbt/pkg/helpers"
"regexp"
)
// ProhibitedSymbolsStrict we can't use these symbols on Windows systems, but can use in *nix
var ProhibitedSymbolsStrict = regexp.MustCompilePOSIX(`[\\/:*?"<>|]`)
func NormalizeSpaceEnding(str string) (string, bool) {
var normalized bool
if string(str[len(str)-1]) == ` ` {
str = str[:len(str)-1] + `_`
normalized = true
}
return str, normalized
}
func FullNormalize(str string) (string, bool) {
var normalized bool
s1 := ProhibitedSymbolsStrict.ReplaceAllString(str, `_`)
if s1 != str {
normalized = true
}
s2 := helpers.HandleCesu8(s1)
if s1 != s2 {
normalized = true
}
s3, n := NormalizeSpaceEnding(s2)
if n {
normalized = true
}
return s3, normalized
}

View File

@ -2,6 +2,8 @@ package torrentStructures
import (
"github.com/rumanzo/bt2qbt/pkg/fileHelpers"
"github.com/rumanzo/bt2qbt/pkg/helpers"
"github.com/rumanzo/bt2qbt/pkg/normalization"
"sort"
)
@ -12,26 +14,49 @@ func (t *Torrent) IsV2OrHybryd() bool {
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
func (t *Torrent) IsSingle() bool {
if t.Single != nil {
return *t.Single
}
single := false
if t.IsV2OrHybryd() {
// v2 torrents always have at least one file that equal torrent name
if len(t.Info.FileTree) == 1 {
torrentName, _ := t.GetNormalizedTorrentName()
if _, ok := t.Info.FileTree[torrentName]; ok {
single = true
}
}
} else {
return *t.FilePathLength
if t.Info.Files == nil {
single = true
}
}
t.Single = &single
return *t.Single
}
// GetFileListWB function that return struct with filelists with bytes from torrent file
func (t *Torrent) GetFileListWB() ([]FilepathLength, bool) {
if t.FilePathLength == nil {
if t.IsV2OrHybryd() { // torrents with v2 or hybrid scheme
result, normalized := getFileListV2(t.Info.FileTree)
t.FilePathLength = &result
return *t.FilePathLength, normalized
} else { // torrent v1 with FileTree
result, normalized := getFileListV1(t)
t.FilePathLength = &result
return *t.FilePathLength, normalized
}
} else {
return *t.FilePathLength, false
}
}
func (t *Torrent) GetFileList() []string {
func (t *Torrent) GetFileList() ([]string, bool) {
var normalized bool
if t.FilePathLength == nil {
t.GetFileListWB()
_, normalized = t.GetFileListWB()
}
if t.FilePaths == nil {
t.FilePaths = &[]string{}
@ -39,29 +64,38 @@ func (t *Torrent) GetFileList() []string {
*t.FilePaths = append(*t.FilePaths, fb.Path)
}
}
return *t.FilePaths
return *t.FilePaths, normalized
}
func getFileListV1(t *Torrent) []FilepathLength {
func getFileListV1(t *Torrent) ([]FilepathLength, bool) {
var normalized bool
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,
})
for _, fileList := range t.Info.Files {
var normalizedFileList []string
if fileList.PathUTF8 != nil {
normalizedFileList = fileList.PathUTF8
} else {
files = append(files, FilepathLength{
Path: fileHelpers.Join(file.Path, `/`),
Length: file.Length,
})
normalizedFileList = fileList.Path
}
for index, filePathPart := range normalizedFileList {
normalizedFilePathPart, gotNormalized := normalization.FullNormalize(filePathPart)
if gotNormalized {
normalized = true
normalizedFileList[index] = normalizedFilePathPart
}
}
files = append(files, FilepathLength{
Path: fileHelpers.Join(normalizedFileList, `/`),
Length: fileList.Length,
})
}
return files
return files, normalized
}
func getFileListV2(f interface{}) []FilepathLength {
nfiles := []FilepathLength{}
func getFileListV2(f interface{}) ([]FilepathLength, bool) {
var normalized bool
var nfiles []FilepathLength
// sort map previously
keys := make([]string, 0, len(f.(map[string]interface{})))
@ -74,12 +108,39 @@ func getFileListV2(f interface{}) []FilepathLength {
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
return nfiles, normalized
}
s, gotNormalized := getFileListV2(v)
if gotNormalized {
normalized = true
}
s := getFileListV2(v)
for _, fpl := range s {
nfiles = append(nfiles, FilepathLength{Path: fileHelpers.Join(append([]string{k}, fpl.Path), `/`), Length: fpl.Length})
normalizedPath, gotNormalized := normalization.FullNormalize(k)
if gotNormalized {
normalized = true
}
nfiles = append(nfiles, FilepathLength{Path: fileHelpers.Join(append([]string{normalizedPath}, fpl.Path), `/`), Length: fpl.Length})
}
}
return nfiles
return nfiles, normalized
}
func (t *Torrent) GetTorrentName() string {
if t.Info.NameUTF8 != "" {
return t.Info.NameUTF8
} else {
return t.Info.Name
}
}
func (t *Torrent) GetNormalizedTorrentName() (string, bool) {
torrentName := t.GetTorrentName()
var normalizedTorrentName string
var normalized bool
if fileHelpers.IsAbs(torrentName) {
normalizedTorrentName, normalized = normalization.NormalizeSpaceEnding(helpers.HandleCesu8(torrentName))
} else {
normalizedTorrentName, normalized = normalization.FullNormalize(torrentName)
}
return normalizedTorrentName, normalized
}

View File

@ -48,6 +48,10 @@ func TestDecodeRealTorrents(t *testing.T) {
name: "008 single v2",
path: "../../test/data/testfile1_single_v2.torrent",
},
{
name: "008 multi emoji prohibited symbols",
path: "../../test/data/tests_slahes_emoji_ut_created.torrent",
},
}
for _, testCase := range cases {
t.Run(testCase.name, func(t *testing.T) {
@ -129,7 +133,7 @@ func TestTorrent_GetFileList(t *testing.T) {
if err != nil {
t.Fatalf("Unexpected error with decoding torrent file: %v", err)
}
list := torrent.GetFileList()
list, _ := torrent.GetFileList()
equal := reflect.DeepEqual(list, testCase.expected)
if !equal && !testCase.mustFail {
changes, err := diff.Diff(list, testCase.expected, diff.DiscardComplexOrigin())
@ -203,6 +207,21 @@ func TestTorrent_GetFileListWB(t *testing.T) {
FilepathLength{Path: "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) {
@ -211,7 +230,7 @@ func TestTorrent_GetFileListWB(t *testing.T) {
if err != nil {
t.Fatalf("Unexpected error with decoding torrent file: %v", err)
}
list := torrent.GetFileListWB()
list, _ := torrent.GetFileListWB()
equal := reflect.DeepEqual(list, testCase.expected)
if !equal && !testCase.mustFail {
changes, err := diff.Diff(list, testCase.expected, diff.DiscardComplexOrigin())
@ -225,3 +244,344 @@ func TestTorrent_GetFileListWB(t *testing.T) {
})
}
}
func TestTorrent_GetFileListV1(t *testing.T) {
type TestCases struct {
name string
torrent *Torrent
expected []FilepathLength
expectedNormalized bool
mustFail bool
}
cases := []TestCases{
{
name: "001 emoji spaces at dir ends prohibited symbols",
torrent: &Torrent{Info: &TorrentInfo{
Files: []*TorrentFile{
&TorrentFile{Path: []string{"file_with_emoji \xed\xa0\xbc\xed\xb6\x95.txt"}, Length: 5},
&TorrentFile{Path: []string{"file_with/slash.txt"}, Length: 5},
&TorrentFile{Path: []string{"testdir_with_emoji_and_space \xed\xa0\xbc\xed\xb6\x95 ", "file_with_emoji \xed\xa0\xbc\xed\xb6\x95.txt"}, Length: 5},
&TorrentFile{Path: []string{"testdir_with_emoji_and_space \xed\xa0\xbc\xed\xb6\x95 ", "file_with/slash.txt"}, Length: 5},
&TorrentFile{Path: []string{"testdir_with_space ", "file_with_emoji \xed\xa0\xbc\xed\xb6\x95.txt"}, Length: 5},
&TorrentFile{Path: []string{"testdir_with_space ", "file_with/slash.txt"}, Length: 5},
},
}},
expectedNormalized: true,
expected: []FilepathLength{
{Path: "file_with_emoji \xf0\x9f\x86\x95.txt", Length: 5},
{Path: "file_with_slash.txt", Length: 5},
{Path: "testdir_with_emoji_and_space \xf0\x9f\x86\x95_/file_with_emoji \xf0\x9f\x86\x95.txt", Length: 5},
{Path: "testdir_with_emoji_and_space \xf0\x9f\x86\x95_/file_with_slash.txt", Length: 5},
{Path: "testdir_with_space_/file_with_emoji \xf0\x9f\x86\x95.txt", Length: 5},
{Path: "testdir_with_space_/file_with_slash.txt", Length: 5},
},
mustFail: false,
},
{
name: "002 mustFail wrong filename",
torrent: &Torrent{Info: &TorrentInfo{
Files: []*TorrentFile{
&TorrentFile{Path: []string{"file_with_emoji \xed\xa0\xbc\xed\xb6\x95.txt"}, Length: 5},
&TorrentFile{Path: []string{"file_with/slash.txt"}, Length: 5},
&TorrentFile{Path: []string{"testdir_with_emoji_and_space \xed\xa0\xbc\xed\xb6\x95 ", "file_with_emoji \xed\xa0\xbc\xed\xb6\x95.txt"}, Length: 5},
&TorrentFile{Path: []string{"testdir_with_emoji_and_space \xed\xa0\xbc\xed\xb6\x95 ", "file_with/slash.txt"}, Length: 5},
&TorrentFile{Path: []string{"testdir_with_space ", "file_with_emoji \xed\xa0\xbc\xed\xb6\x95.txt"}, Length: 5},
&TorrentFile{Path: []string{"testdir_with_space ", "file_with/slash.txt"}, Length: 5},
},
}},
expectedNormalized: true,
expected: []FilepathLength{
{Path: "file_with_emoji \xf0\x9f\x86\x95.txt", Length: 5},
{Path: "file_with_slash.txt", Length: 5},
{Path: "testdir_with_emoji_and_space \xf0\x9f\x86\x95_/file_with_emoji \xf0\x9f\x86\x95.txt", Length: 5},
{Path: "testdir_with_emoji_and_space \xf0\x9f\x86\x95_/file_with_slash.txt", Length: 5},
{Path: "testdir_with_space_/file_with_emoji \xf0\x9f\x86\x95.txt", Length: 5},
{Path: "testdir_with_space_/file__with_slash.txt", Length: 5},
},
mustFail: true,
},
{
name: "003 emoji spaces at dir ends prohibited symbols mustfail due returned false normalized",
torrent: &Torrent{Info: &TorrentInfo{
Files: []*TorrentFile{
&TorrentFile{Path: []string{"file_with_emoji \xed\xa0\xbc\xed\xb6\x95.txt"}, Length: 5},
&TorrentFile{Path: []string{"file_with/slash.txt"}, Length: 5},
&TorrentFile{Path: []string{"testdir_with_emoji_and_space \xed\xa0\xbc\xed\xb6\x95 ", "file_with_emoji \xed\xa0\xbc\xed\xb6\x95.txt"}, Length: 5},
&TorrentFile{Path: []string{"testdir_with_emoji_and_space \xed\xa0\xbc\xed\xb6\x95 ", "file_with/slash.txt"}, Length: 5},
&TorrentFile{Path: []string{"testdir_with_space ", "file_with_emoji \xed\xa0\xbc\xed\xb6\x95.txt"}, Length: 5},
&TorrentFile{Path: []string{"testdir_with_space ", "file_with/slash.txt"}, Length: 5},
},
}},
expectedNormalized: false,
expected: []FilepathLength{
{Path: "file_with_emoji \xf0\x9f\x86\x95.txt", Length: 5},
{Path: "file_with_slash.txt", Length: 5},
{Path: "testdir_with_emoji_and_space \xf0\x9f\x86\x95_/file_with_emoji \xf0\x9f\x86\x95.txt", Length: 5},
{Path: "testdir_with_emoji_and_space \xf0\x9f\x86\x95_/file_with_slash.txt", Length: 5},
{Path: "testdir_with_space_/file_with_emoji \xf0\x9f\x86\x95.txt", Length: 5},
{Path: "testdir_with_space_/file_with_slash.txt", Length: 5},
},
mustFail: true,
},
{
name: "004 normal torrent",
torrent: &Torrent{Info: &TorrentInfo{
Files: []*TorrentFile{
&TorrentFile{Path: []string{"file.txt"}, Length: 5},
&TorrentFile{Path: []string{"testdir", "file.txt"}, Length: 5},
},
}},
expectedNormalized: false,
expected: []FilepathLength{
{Path: "file.txt", Length: 5},
{Path: "testdir/file.txt", Length: 5},
},
mustFail: false,
},
}
for _, testCase := range cases {
t.Run(testCase.name, func(t *testing.T) {
filePathLength, normalized := getFileListV1(testCase.torrent)
equal := reflect.DeepEqual(filePathLength, testCase.expected)
if !equal && !testCase.mustFail {
changes, err := diff.Diff(filePathLength, testCase.expected, diff.DiscardComplexOrigin())
if err != nil {
t.Error(err.Error())
}
t.Fatalf("Unexpected error: structures isn't equal:\n Got: %#v\n Expect %#v\n Diff: %v\n", filePathLength, testCase.expected, spew.Sdump(changes))
}
if normalized != testCase.expectedNormalized && !testCase.mustFail {
t.Fatalf("Normalization expected %v, got %v", testCase.expectedNormalized, normalized)
}
})
}
}
func TestTorrent_GetFileListV2(t *testing.T) {
type TestCases struct {
name string
torrent *Torrent
expected []FilepathLength
expectedNormalized bool
mustFail bool
}
cases := []TestCases{
{
name: "001 emoji spaces at dir ends prohibited symbols",
torrent: &Torrent{Info: &TorrentInfo{
FileTree: map[string]interface{}{
"file_with_emoji \xed\xa0\xbc\xed\xb6\x95.txt": map[string]interface{}{
``: map[string]interface{}{
`length`: int64(5),
`pieces_root`: []byte{},
},
},
"file_with/slash.txt": map[string]interface{}{
``: map[string]interface{}{
`length`: int64(5),
`pieces_root`: []byte{},
},
},
"testdir_with_emoji_and_space \xed\xa0\xbc\xed\xb6\x95 ": map[string]interface{}{
"file_with_emoji \xed\xa0\xbc\xed\xb6\x95.txt": map[string]interface{}{
``: map[string]interface{}{
`length`: int64(5),
`pieces_root`: []byte{},
},
},
"file_with/slash.txt": map[string]interface{}{
``: map[string]interface{}{
`length`: int64(5),
`pieces_root`: []byte{},
},
},
},
"testdir_with_space ": map[string]interface{}{
"file_with_emoji \xed\xa0\xbc\xed\xb6\x95.txt": map[string]interface{}{
``: map[string]interface{}{
`length`: int64(5),
`pieces_root`: []byte{},
},
},
"file_with/slash.txt": map[string]interface{}{
``: map[string]interface{}{
`length`: int64(5),
`pieces_root`: []byte{},
},
},
},
},
}},
expectedNormalized: true,
expected: []FilepathLength{
{Path: "file_with_slash.txt", Length: 5},
{Path: "file_with_emoji \xf0\x9f\x86\x95.txt", Length: 5},
{Path: "testdir_with_emoji_and_space \xf0\x9f\x86\x95_/file_with_slash.txt", Length: 5},
{Path: "testdir_with_emoji_and_space \xf0\x9f\x86\x95_/file_with_emoji \xf0\x9f\x86\x95.txt", Length: 5},
{Path: "testdir_with_space_/file_with_slash.txt", Length: 5},
{Path: "testdir_with_space_/file_with_emoji \xf0\x9f\x86\x95.txt", Length: 5},
},
mustFail: false,
},
{
name: "002 mustFail wrong filename",
torrent: &Torrent{Info: &TorrentInfo{
FileTree: map[string]interface{}{
"file_with_emoji \xed\xa0\xbc\xed\xb6\x95.txt": map[string]interface{}{
``: map[string]interface{}{
`length`: int64(5),
`pieces_root`: []byte{},
},
},
"file_with/slash.txt": map[string]interface{}{
``: map[string]interface{}{
`length`: int64(5),
`pieces_root`: []byte{},
},
},
"testdir_with_emoji_and_space \xed\xa0\xbc\xed\xb6\x95 ": map[string]interface{}{
"file_with_emoji \xed\xa0\xbc\xed\xb6\x95.txt": map[string]interface{}{
``: map[string]interface{}{
`length`: int64(5),
`pieces_root`: []byte{},
},
},
"file_with/slash.txt": map[string]interface{}{
``: map[string]interface{}{
`length`: int64(5),
`pieces_root`: []byte{},
},
},
},
"testdir_with_space ": map[string]interface{}{
"file_with_emoji \xed\xa0\xbc\xed\xb6\x95.txt": map[string]interface{}{
``: map[string]interface{}{
`length`: int64(5),
`pieces_root`: []byte{},
},
},
"file_with/slash.txt": map[string]interface{}{
``: map[string]interface{}{
`length`: int64(5),
`pieces_root`: []byte{},
},
},
},
},
}},
expectedNormalized: true,
expected: []FilepathLength{
{Path: "file_with_slash.txt", Length: 5},
{Path: "file_with_emoji \xf0\x9f\x86\x95.txt", Length: 5},
{Path: "testdir_with_emoji_and_space \xf0\x9f\x86\x95_/file_with_slash.txt", Length: 5},
{Path: "testdir_with_emoji_and_space \xf0\x9f\x86\x95_/file_with_emoji \xf0\x9f\x86\x95.txt", Length: 5},
{Path: "testdir_with_space_/file_with_slash.txt", Length: 5},
{Path: "testdir_with_space_/file_with_emoji_ \xf0\x9f\x86\x95.txt", Length: 5},
},
mustFail: true,
},
{
name: "003 emoji spaces at dir ends prohibited symbols mustfail due returned false normalized",
torrent: &Torrent{Info: &TorrentInfo{
FileTree: map[string]interface{}{
"file_with_emoji \xed\xa0\xbc\xed\xb6\x95.txt": map[string]interface{}{
``: map[string]interface{}{
`length`: int64(5),
`pieces_root`: []byte{},
},
},
"file_with/slash.txt": map[string]interface{}{
``: map[string]interface{}{
`length`: int64(5),
`pieces_root`: []byte{},
},
},
"testdir_with_emoji_and_space \xed\xa0\xbc\xed\xb6\x95 ": map[string]interface{}{
"file_with_emoji \xed\xa0\xbc\xed\xb6\x95.txt": map[string]interface{}{
``: map[string]interface{}{
`length`: int64(5),
`pieces_root`: []byte{},
},
},
"file_with/slash.txt": map[string]interface{}{
``: map[string]interface{}{
`length`: int64(5),
`pieces_root`: []byte{},
},
},
},
"testdir_with_space ": map[string]interface{}{
"file_with_emoji \xed\xa0\xbc\xed\xb6\x95.txt": map[string]interface{}{
``: map[string]interface{}{
`length`: int64(5),
`pieces_root`: []byte{},
},
},
"file_with/slash.txt": map[string]interface{}{
``: map[string]interface{}{
`length`: int64(5),
`pieces_root`: []byte{},
},
},
},
},
}},
expectedNormalized: false,
expected: []FilepathLength{
{Path: "file_with_slash.txt", Length: 5},
{Path: "file_with_emoji \xf0\x9f\x86\x95.txt", Length: 5},
{Path: "testdir_with_emoji_and_space \xf0\x9f\x86\x95_/file_with_slash.txt", Length: 5},
{Path: "testdir_with_emoji_and_space \xf0\x9f\x86\x95_/file_with_emoji \xf0\x9f\x86\x95.txt", Length: 5},
{Path: "testdir_with_space_/file_with_slash.txt", Length: 5},
{Path: "testdir_with_space_/file_with_emoji_ \xf0\x9f\x86\x95.txt", Length: 5},
},
mustFail: true,
},
{
name: "004 normal torrent",
torrent: &Torrent{Info: &TorrentInfo{
Files: []*TorrentFile{
&TorrentFile{Path: []string{"file.txt"}, Length: 5},
&TorrentFile{Path: []string{"testdir", "file.txt"}, Length: 5},
},
FileTree: map[string]interface{}{
"file.txt": map[string]interface{}{
``: map[string]interface{}{
`length`: int64(5),
`pieces_root`: []byte{},
},
},
"testdir": map[string]interface{}{
"file.txt": map[string]interface{}{
``: map[string]interface{}{
`length`: int64(5),
`pieces_root`: []byte{},
},
},
},
},
}},
expectedNormalized: false,
expected: []FilepathLength{
{Path: "file.txt", Length: 5},
{Path: "testdir/file.txt", Length: 5},
},
mustFail: false,
},
}
for _, testCase := range cases {
t.Run(testCase.name, func(t *testing.T) {
filePathLength, normalized := getFileListV2(testCase.torrent.Info.FileTree)
equal := reflect.DeepEqual(filePathLength, testCase.expected)
if !equal && !testCase.mustFail {
changes, err := diff.Diff(filePathLength, testCase.expected, diff.DiscardComplexOrigin())
if err != nil {
t.Error(err.Error())
}
t.Fatalf("Unexpected error: structures isn't equal:\n Got: %#v\nExpected: %#v\nDiff: %v\n", filePathLength, testCase.expected, spew.Sdump(changes))
}
if normalized != testCase.expectedNormalized && !testCase.mustFail {
t.Fatalf("Normalization expected %v, got %v", testCase.expectedNormalized, normalized)
}
})
}
}

View File

@ -11,6 +11,7 @@ type Torrent struct {
PieceLayers *map[string]interface{} `bencode:"piece layers"`
FilePathLength *[]FilepathLength `bencode:"-"` // service field
FilePaths *[]string `bencode:"-"` // service field
Single *bool `bencode:"-"` // service field
}
type TorrentInfo struct {

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 datei1701616527e8:encoding5:UTF-84:infod5:filesld6:lengthi8e4:pathl26:file_with_emoji_😊.txteed6:lengthi8e4:pathl19:file_with_slash.txteed6:lengthi8e4:pathl34:testdit_with_emoji_and_space😊26:file_with_emoji_😊.txteed6:lengthi8e4:pathl34:testdit_with_emoji_and_space😊19:file_with_slash.txteed6:lengthi8e4:pathl18:testdit_with_space26:file_with_emoji_😊.txteed6:lengthi8e4:pathl18:testdit_with_space19:file_with_slash.txteee4:name25:tests_slahes_emoji 😊12:piece lengthi16384e6:pieces20:ûtYlYã<59><18>ìñÕdÙË<C399>wUee