bt2qbt/internal/transfer/resumeHandle.go
Alexey Kostin cca7869d2a
Full refactor. Introduce tests (#35)
* start refactor

* default golang loyaout
separate qbittorrent and torrent structures

* stage

* move flags handle to module and cover it with tests
change handle sep with path.join

* ugly remove unneeded fields from resume.dat

* use path joing
prepare for resume structs

* Introduce subtests

* fix behavior

* Move to structures instead of using interfaces

* Make main cmd clean, decomposite functions

* Make main cmd clean, decomposite functions

* Detect torrents functions and tests

* Fixes

* Fixes

* New tests for content layout and file paths
reformat code for project
change libtorrent version
append torrent v2 schema in torrent structs

* filepath helper module
fix some tests

* fileHelpers new functions and tests

* new function with cut last part with tests

* function description and small fix

* rename some variables and structures

* beautiful tests handle for savePaths
starting refactor savepaths

* Refactor HandleSavePath func. Cover with tests
Move replace field creation to handle and assign pointer, instead of generate each time with handle resumeItem.
Moved opts to transferStructure.

* fix some tests

* HandleSavePaths covered with tests and little bit fixed

* Another one additional test for fully function cover

* check for tests

* Remove PiecePriority

* Refactored HandlePieces functions
Removed unused functions

* Rename variables. Introduced new tests

* Append tests

* Rename test funcs

* New tests

* New tests

* move modules

* introduce Makefile

* Adapt fileHelpers for both windows and linux. Linux tests passed
Use slashed paths for search torrents

* remove test template

* test fileset

* Makefile. Version introduce

* Fix darwin i386 version build

* Bug fix with mappedfiles
Need more tests and append new functionality.

* tests for HandleSavePaths with absoulte paths in mapped files

* additional tests with fileshare

* all tests OK

* Fix torrent state. Added tests

* fixes after code inspection

* Magnet link support. Tests

* README.md update

Co-authored-by: Alexey Kostin <a.kostin@corp.mail.ru>
2022-03-30 02:30:29 +03:00

167 lines
5.3 KiB
Go

package transfer
import (
"fmt"
"github.com/rumanzo/bt2qbt/internal/options"
"github.com/rumanzo/bt2qbt/pkg/fileHelpers"
"github.com/rumanzo/bt2qbt/pkg/helpers"
"github.com/rumanzo/bt2qbt/pkg/torrentStructures"
"github.com/rumanzo/bt2qbt/pkg/utorrentStructs"
"log"
"os"
"path/filepath"
"runtime"
"runtime/debug"
"strings"
"sync"
)
func HandleResumeItem(key string, transferStruct *TransferStructure, chans *Channels, wg *sync.WaitGroup) error {
//panic recover
defer wg.Done()
defer func() {
<-chans.BoundedChannel
}()
defer func() {
if r := recover(); r != nil {
chans.ErrChannel <- fmt.Sprintf(
"Panic while processing torrent %v:\n======\nReason: %v.\nText panic:\n%v\n======",
key, r, string(debug.Stack()))
}
}()
var err error
HandleTorrentFilePath(transferStruct, key)
err = FindTorrentFile(transferStruct)
if err != nil {
chans.ErrChannel <- err.Error()
return err
}
// struct for work with
err = helpers.DecodeTorrentFile(transferStruct.TorrentFilePath, transferStruct.TorrentFile)
if err != nil {
chans.ErrChannel <- fmt.Sprintf("Can't decode torrent file %v for %v", transferStruct.TorrentFilePath, key)
return err
}
// because hash of info very important it will be better to use interface for get hash
if !strings.HasPrefix(key, "magnet:?") {
err = helpers.DecodeTorrentFile(transferStruct.TorrentFilePath, &transferStruct.TorrentFileRaw)
if err != nil {
chans.ErrChannel <- fmt.Sprintf("Can't decode torrent file %v for %v", transferStruct.TorrentFilePath, key)
return err
}
} else {
transferStruct.Magnet = true
transferStruct.TorrentFile = &torrentStructures.Torrent{
Info: &torrentStructures.TorrentInfo{},
}
}
transferStruct.HandleStructures()
newBaseName := transferStruct.GetHash()
if err = helpers.EncodeTorrentFile(filepath.Join(transferStruct.Opts.QBitDir, newBaseName+".fastresume"), transferStruct.Fastresume); err != nil {
chans.ErrChannel <- fmt.Sprintf("Can't create qBittorrent fastresume file %v. With error: %v", filepath.Join(transferStruct.Opts.QBitDir, newBaseName+".fastresume"), err)
return err
}
if err = helpers.CopyFile(transferStruct.TorrentFilePath, filepath.Join(transferStruct.Opts.QBitDir, newBaseName+".torrent")); err != nil {
chans.ErrChannel <- fmt.Sprintf("Can't create qBittorrent torrent file %v", filepath.Join(transferStruct.Opts.QBitDir, newBaseName+".torrent"))
return err
}
chans.ComChannel <- fmt.Sprintf("Sucessfully imported %v", key)
return nil
}
func HandleResumeItems(opts *options.Opts, resumeItems map[string]*utorrentStructs.ResumeItem) {
totalJobs := len(resumeItems)
chans := Channels{ComChannel: make(chan string, totalJobs),
ErrChannel: make(chan string, totalJobs),
BoundedChannel: make(chan bool, runtime.GOMAXPROCS(0)*2)}
numJob := 1
var newTags []string
var wg sync.WaitGroup
positionNum := 0
replaces := CreateReplaces(opts.Replaces)
for key, resumeItem := range resumeItems {
positionNum++
if opts.WithoutTags == false {
if resumeItem.Labels != nil {
for _, label := range resumeItem.Labels {
if exists, tag := helpers.CheckExists(helpers.ASCIIConvert(label), newTags); !exists {
newTags = append(newTags, tag)
}
}
}
wg.Add(1)
chans.BoundedChannel <- true
transferStruct := CreateEmptyNewTransferStructure()
transferStruct.ResumeItem = resumeItem
transferStruct.Replace = replaces
transferStruct.Opts = opts
go HandleResumeItem(key, &transferStruct, &chans, &wg)
} else {
totalJobs--
}
}
go func() {
wg.Wait()
close(chans.ComChannel)
close(chans.ErrChannel)
}()
for message := range chans.ComChannel {
fmt.Printf("%v/%v %v \n", numJob, totalJobs, message)
numJob++
}
var wasErrors bool
for message := range chans.ErrChannel {
fmt.Printf("%v/%v %v \n", numJob, totalJobs, message)
wasErrors = true
numJob++
}
if opts.WithoutTags == false {
ProcessLabels(opts, newTags)
}
fmt.Println()
log.Println("Ended")
if wasErrors {
log.Println("Not all torrents was processed")
}
}
// HandleTorrentFilePath check if resume key is absolute path. It means that we should search torrent file using this absolute path
// notice that torrent file name always known
func HandleTorrentFilePath(transferStructure *TransferStructure, key string) {
if fileHelpers.IsAbs(key) {
transferStructure.TorrentFilePath = fileHelpers.Normalize(key, `/`)
transferStructure.TorrentFileName = fileHelpers.Base(key)
} else {
transferStructure.TorrentFilePath = fileHelpers.Join([]string{transferStructure.Opts.BitDir, key}, `/`) // additional search required
transferStructure.TorrentFileName = key
}
}
// 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 {
// normalize path with os.PathSeparator, because file can be on share, for example
fullPath := fileHelpers.Join([]string{searchPath, transferStructure.TorrentFileName}, string(os.PathSeparator))
if _, err = os.Stat(fullPath); err == nil {
transferStructure.TorrentFilePath = fullPath
return nil
}
}
// return error only if we didn't find anything
return fmt.Errorf("can't locate torrent file %v", transferStructure.TorrentFileName)
}
return nil
}