Skip to content

Commit

Permalink
Fix windows prohibited simbols (#46)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
rumanzo authored Dec 5, 2023
1 parent a25c267 commit 0b305dd
Show file tree
Hide file tree
Showing 15 changed files with 1,076 additions and 97 deletions.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
![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)
> [!IMPORTANT]
> 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)
Expand Down
7 changes: 1 addition & 6 deletions internal/transfer/fastresumeHandle.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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()
}
12 changes: 3 additions & 9 deletions internal/transfer/labels.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"errors"
"fmt"
"github.com/rumanzo/bt2qbt/internal/options"
"io/ioutil"
"os"
)

Expand All @@ -14,24 +13,19 @@ 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 {
return errors.New(fmt.Sprintf("Unexpected error while open categories.json. Error:\n%v\n", err))
}

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))
Expand All @@ -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))
}
Expand Down
3 changes: 1 addition & 2 deletions internal/transfer/labels_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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())
}
Expand Down
2 changes: 1 addition & 1 deletion internal/transfer/resumeHandle.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
50 changes: 20 additions & 30 deletions internal/transfer/transfer.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -15,7 +16,6 @@ import (
"regexp"
"strings"
"time"
"unicode/utf8"
)

//goland:noinspection GoNameStartsWithPackageName
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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)
Expand All @@ -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
Expand Down Expand Up @@ -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
}
var nameNormalized bool
transfer.Fastresume.Name, nameNormalized = normalization.FullNormalize(transfer.TorrentFile.GetTorrentName())

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 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 {
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 {
Expand All @@ -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)
}
}
}
Expand All @@ -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 {
Expand All @@ -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}
}
Expand Down
Loading

0 comments on commit 0b305dd

Please sign in to comment.