Skip to content

Commit

Permalink
infoHash list args now accept '-' as reading from stdin
Browse files Browse the repository at this point in the history
  • Loading branch information
sagan committed Feb 27, 2024
1 parent efb26ea commit 8205a20
Show file tree
Hide file tree
Showing 38 changed files with 497 additions and 232 deletions.
13 changes: 10 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,7 @@ ptool clientctl local global_upload_speed_limit=10M
ptool <command> <client> [flags] [<infoHash>...]
```

`<infoHash>` 参数为指定的 BT 客户端里需要操作的种子的 infoHash 列表。也可以使用以下特殊值参数操作多个种子(delete 命令根据除 infoHash 以外的条件删除种子时需要二次确认)
`<infoHash>` 参数为指定的 BT 客户端里需要操作的种子的 infoHash 列表。也可以使用以下特殊值参数操作多个种子:

- `_all` : 所有种子
- `_done` : 所有已下载完成的种子(无论是否正在做种)(`_seeding` | `_completed`)
Expand All @@ -269,13 +269,20 @@ ptool resume local _all
# 暂停 abc 分类下的所有正在下载种子
ptool pause local --category abc _downloading
# 从客户端删除指定种子(默认同时删除文件)
# 从客户端删除指定种子(默认同时删除文件)。默认会提示确认删除,除非指定 --force 参数
ptool delete local 31a615d5984cb63c6f999f72bb3961dce49c194a
# 特别的,如果 show 命令只提供一个 infoHash 参数,会显示该种子的所有详细信息
# 特别的,如果 show 命令只提供一个 infoHash 参数,会显示该种子的所有详细信息
ptool show local 31a615d5984cb63c6f999f72bb3961dce49c194a
```

`show` 以外的命令可以只传入一个特殊的 `-` 作为参数,视为从 stdin 读取 infoHash 列表。而 `show` 命令提供很多参数可以用于筛选种子,并且可以使用 `--show-info-hash-only` 参数只输出匹配的种子的 infoHash。因此可以组合使用 `show` 命令和其它命令,例如:

```
# 删除 local 客户端里 "rss" 分类里已经下载完成超过5天的种子
ptool show local --category rss --completed-before 5d --show-info-hash-only | ptool delete local --force -
```

#### 管理 BT 客户端里的的种子分类 / 标签 / Trackers 等(getcategories / createcategory / removecategories / setcategory / gettags / createtags / deletetags / addtags / removetags / renametag / edittracker / addtrackers / removetrackers / setsavepath)

```
Expand Down
40 changes: 21 additions & 19 deletions client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ type Torrent struct {
LowLevelState string // original state value returned by bt client
Atime int64 // timestamp torrent added
Ctime int64 // timestamp torrent completed. <=0 if not completed.
ActivityTime int64 // timestamp of torrent latest activity (a chunk being downloaded / uploaded)
Category string
SavePath string
ContentPath string
Expand Down Expand Up @@ -386,31 +387,32 @@ func PrintTorrentFiles(files []TorrentContentFile, showRaw bool) {
}
}

func PrintTorrent(torrent *Torrent) {
func (torrent *Torrent) Print() {
ctimeStr := "-"
if torrent.Ctime > 0 {
ctimeStr = util.FormatTime(torrent.Ctime)
}
fmt.Printf("Torrent name: %s\n", torrent.Name)
fmt.Printf("InfoHash: %s\n", torrent.InfoHash)
fmt.Printf("Size: %s (%d)\n", util.BytesSize(float64(torrent.Size)), torrent.Size)
fmt.Printf("Process: %d%%\n", int64(float64(torrent.SizeCompleted)*100/float64(torrent.Size)))
fmt.Printf("Total Size: %s (%d)\n", util.BytesSize(float64(torrent.SizeTotal)), torrent.SizeTotal)
fmt.Printf("State (LowLevelState): %s (%s)\n", torrent.State, torrent.LowLevelState)
fmt.Printf("Speeds: ↓S: %s/s | ↑S: %s/s\n",
fmt.Printf("- InfoHash: %s\n", torrent.InfoHash)
fmt.Printf("- Size: %s (%d)\n", util.BytesSize(float64(torrent.Size)), torrent.Size)
fmt.Printf("- Process: %d%%\n", int64(float64(torrent.SizeCompleted)*100/float64(torrent.Size)))
fmt.Printf("- Total Size: %s (%d)\n", util.BytesSize(float64(torrent.SizeTotal)), torrent.SizeTotal)
fmt.Printf("- State (LowLevelState): %s (%s)\n", torrent.State, torrent.LowLevelState)
fmt.Printf("- Speeds: ↓S: %s/s | ↑S: %s/s\n",
util.BytesSize(float64(torrent.DownloadSpeed)),
util.BytesSize(float64(torrent.UploadSpeed)),
)
fmt.Printf("Category: %s\n", torrent.Category)
fmt.Printf("Tags: %s\n", strings.Join(torrent.Tags, ","))
fmt.Printf("Meta: %v\n", torrent.Meta)
fmt.Printf("Add time: %s\n", util.FormatTime(torrent.Atime))
fmt.Printf("Completion time: %s\n", ctimeStr)
fmt.Printf("Tracker: %s\n", torrent.Tracker)
fmt.Printf("Seeders / Peers: %d / %d\n", torrent.Seeders, torrent.Leechers)
fmt.Printf("Save path: %s\n", torrent.SavePath)
fmt.Printf("Content path: %s\n", torrent.ContentPath)
fmt.Printf("Downloaded / Uploaded: %s / %s\n",
fmt.Printf("- Category: %s\n", torrent.Category)
fmt.Printf("- Tags: %s\n", strings.Join(torrent.Tags, ","))
fmt.Printf("- Meta: %v\n", torrent.Meta)
fmt.Printf("- Add time: %s\n", util.FormatTime(torrent.Atime))
fmt.Printf("- Completion time: %s\n", ctimeStr)
fmt.Printf("- Last activity time: %s\n", util.FormatTime(torrent.ActivityTime))
fmt.Printf("- Tracker: %s\n", torrent.Tracker)
fmt.Printf("- Seeders / Peers: %d / %d\n", torrent.Seeders, torrent.Leechers)
fmt.Printf("- Save path: %s\n", torrent.SavePath)
fmt.Printf("- Content path: %s\n", torrent.ContentPath)
fmt.Printf("- Downloaded / Uploaded: %s / %s\n",
util.BytesSize(float64(torrent.Downloaded)),
util.BytesSize(float64(torrent.Uploaded)),
)
Expand Down Expand Up @@ -506,7 +508,7 @@ func QueryTorrents(clientInstance Client, category string, tag string, filter st
}
var tags []string
if tag != "" {
tags = strings.Split(tag, ",")
tags = util.SplitCsv(tag)
}
torrents2 := []Torrent{}
for _, torrent := range torrents {
Expand Down Expand Up @@ -557,7 +559,7 @@ func SelectTorrents(clientInstance Client, category string, tag string, filter s
}
var tags []string
if tag != "" {
tags = strings.Split(tag, ",")
tags = util.SplitCsv(tag)
}
infoHashes := []string{}
for _, torrent := range torrents {
Expand Down
5 changes: 2 additions & 3 deletions client/qbittorrent/api.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package qbittorrent

import (
"strings"

"github.com/sagan/ptool/client"
"github.com/sagan/ptool/util"
)
Expand Down Expand Up @@ -327,6 +325,7 @@ func (qbtorrent *apiTorrentInfo) ToTorrent() *client.Torrent {
LowLevelState: qbtorrent.State,
Atime: qbtorrent.Added_on,
Ctime: qbtorrent.Completion_on,
ActivityTime: qbtorrent.Last_activity,
Downloaded: qbtorrent.Downloaded,
DownloadSpeed: qbtorrent.Dlspeed,
DownloadSpeedLimit: qbtorrent.Dl_limit,
Expand All @@ -336,7 +335,7 @@ func (qbtorrent *apiTorrentInfo) ToTorrent() *client.Torrent {
Category: qbtorrent.Category,
SavePath: qbtorrent.Save_path,
ContentPath: qbtorrent.Content_path,
Tags: strings.Split(qbtorrent.Tags, ","),
Tags: util.SplitCsv(qbtorrent.Tags),
Seeders: qbtorrent.Num_complete,
Size: qbtorrent.Size,
SizeCompleted: qbtorrent.Completed,
Expand Down
2 changes: 1 addition & 1 deletion client/qbittorrent/qbittorrent.go
Original file line number Diff line number Diff line change
Expand Up @@ -532,7 +532,7 @@ func (qbclient *Client) ModifyTorrent(infoHash string,
}

if len(option.Tags) > 0 || len(option.RemoveTags) > 0 {
qbTags := strings.Split(qbtorrent.Tags, ",")
qbTags := util.SplitCsv(qbtorrent.Tags)
addTags := []string{}
removeTags := []string{}
for _, addTag := range option.Tags {
Expand Down
1 change: 1 addition & 0 deletions client/transmission/transmission.go
Original file line number Diff line number Diff line change
Expand Up @@ -915,6 +915,7 @@ func tr2Torrent(trtorrent *transmissionrpc.Torrent) *client.Torrent {
LowLevelState: fmt.Sprint(trtorrent.Status),
Atime: trtorrent.AddedDate.Unix(),
Ctime: trtorrent.DoneDate.Unix(), // 0 if not completed
ActivityTime: trtorrent.ActivityDate.Unix(),
Downloaded: *trtorrent.DownloadedEver,
DownloadSpeed: *trtorrent.RateDownload,
DownloadSpeedLimit: downloadSpeedLimit,
Expand Down
2 changes: 1 addition & 1 deletion cmd/add/add.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ func add(cmd *cobra.Command, args []string) error {
}
var fixedTags []string
if addTags != "" {
fixedTags = strings.Split(addTags, ",")
fixedTags = util.SplitCsv(addTags)
}
domainSiteMap := map[string]string{}
siteInstanceMap := map[string]site.Site{}
Expand Down
26 changes: 19 additions & 7 deletions cmd/addtags/addtags.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ package addtags

import (
"fmt"
"strings"

"github.com/spf13/cobra"

"github.com/sagan/ptool/client"
"github.com/sagan/ptool/cmd"
"github.com/sagan/ptool/util"
)

var command = &cobra.Command{
Expand All @@ -17,7 +17,8 @@ var command = &cobra.Command{
Long: `Add tags to torrents in client.
{tags} : comma-seperated tags list.
[infoHash]...: infoHash list of torrents. It's possible to use state filter to target multiple torrents:
_all, _active, _done, _undone, _downloading, _seeding, _paused, _completed, _error.`,
_all, _active, _done, _undone, _downloading, _seeding, _paused, _completed, _error.
Specially, use a single "-" as args to read infoHash list from stdin, delimited by blanks.`,
Args: cobra.MatchAll(cobra.MinimumNArgs(2), cobra.OnlyValidArgs),
RunE: addtags,
}
Expand All @@ -38,17 +39,28 @@ func init() {

func addtags(cmd *cobra.Command, args []string) error {
clientName := args[0]
tags := strings.Split(args[1], ",")
args = args[2:]
if category == "" && tag == "" && filter == "" && len(args) == 0 {
return fmt.Errorf("you must provide at least a condition flag or hashFilter")
tags := util.SplitCsv(args[1])
infoHashes := args[2:]
if category == "" && tag == "" && filter == "" {
if len(infoHashes) == 0 {
return fmt.Errorf("you must provide at least a condition flag or hashFilter")
}
if len(infoHashes) == 1 && infoHashes[0] == "-" {
if data, err := util.ReadArgsFromStdin(); err != nil {
return fmt.Errorf("failed to parse stdin to info hashes: %v", err)
} else if len(data) == 0 {
return nil
} else {
infoHashes = data
}
}
}
clientInstance, err := client.CreateClient(clientName)
if err != nil {
return fmt.Errorf("failed to create client: %v", err)
}

infoHashes, err := client.SelectTorrents(clientInstance, category, tag, filter, args...)
infoHashes, err = client.SelectTorrents(clientInstance, category, tag, filter, infoHashes...)
if err != nil {
return err
}
Expand Down
20 changes: 16 additions & 4 deletions cmd/addtrackers/addtrackers.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ var command = &cobra.Command{
Long: `Add new trackers to torrents of client.
[infoHash]...: infoHash list of torrents. It's possible to use state filter to target multiple torrents:
_all, _active, _done, _undone, _downloading, _seeding, _paused, _completed, _error.
Specially, use a single "-" as args to read infoHash list from stdin, delimited by blanks.
Example:
ptool addtrackers <client> <infoHashes...> --tracker "https://..."
Expand Down Expand Up @@ -51,9 +52,20 @@ func init() {

func addtrackers(cmd *cobra.Command, args []string) error {
clientName := args[0]
args = args[1:]
if category == "" && tag == "" && filter == "" && len(args) == 0 {
return fmt.Errorf("you must provide at least a condition flag or hashFilter")
infoHashes := args[1:]
if category == "" && tag == "" && filter == "" {
if len(infoHashes) == 0 {
return fmt.Errorf("you must provide at least a condition flag or hashFilter")
}
if len(infoHashes) == 1 && infoHashes[0] == "-" {
if data, err := util.ReadArgsFromStdin(); err != nil {
return fmt.Errorf("failed to parse stdin to info hashes: %v", err)
} else if len(data) == 0 {
return nil
} else {
infoHashes = data
}
}
}
if len(trackers) == 0 {
return fmt.Errorf("at least an --tracker MUST be provided")
Expand All @@ -68,7 +80,7 @@ func addtrackers(cmd *cobra.Command, args []string) error {
return fmt.Errorf("failed to create client: %v", err)
}

torrents, err := client.QueryTorrents(clientInstance, category, tag, filter, args...)
torrents, err := client.QueryTorrents(clientInstance, category, tag, filter, infoHashes...)
if err != nil {
return err
}
Expand Down
19 changes: 9 additions & 10 deletions cmd/batchdl/batchdl.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (
"fmt"
"os"
"os/signal"
"strings"
"syscall"

log "github.com/sirupsen/logrus"
Expand Down Expand Up @@ -81,7 +80,7 @@ func init() {
command.Flags().BoolVarP(&newestFlag, "newest", "n", false,
`Download newest torrents of site. Equivalent with "--sort time --order desc --one-page"`)
command.Flags().BoolVarP(&addRespectNoadd, "add-respect-noadd", "", false,
"Used with '--action add'. Check and respect _noadd flag in client")
`Used with "--action add". Check and respect _noadd flag in client`)
command.Flags().BoolVarP(&nohr, "no-hr", "", false,
"Skip torrent that has any type of HnR (Hit and Run) restriction")
command.Flags().BoolVarP(&allowBreak, "break", "", false,
Expand Down Expand Up @@ -114,17 +113,17 @@ func init() {
command.Flags().StringVarP(&startPage, "start-page", "", "",
"Start fetching torrents from here (should be the returned LastPage value last time you run this command)")
command.Flags().StringVarP(&downloadDir, "download-dir", "", ".",
"Used with '--action download'. Set the local dir of downloaded torrents. Default == current dir")
`Used with "--action download". Set the local dir of downloaded torrents. Default == current dir`)
command.Flags().StringVarP(&addClient, "add-client", "", "",
"Used with '--action add'. Set the client. Required in this action")
`Used with "--action add". Set the client. Required in this action`)
command.Flags().StringVarP(&addCategory, "add-category", "", "",
"Used with '--action add'. Set the category when adding torrent to client")
`Used with "--action add". Set the category when adding torrent to client`)
command.Flags().StringVarP(&addTags, "add-tags", "", "",
"Used with '--action add'. Set the tags when adding torrent to client (comma-separated)")
`Used with "--action add". Set the tags when adding torrent to client (comma-separated)`)
command.Flags().StringVarP(&savePath, "add-save-path", "", "",
"Set contents save path of added torrents")
command.Flags().StringVarP(&exportFile, "export-file", "", "",
"Used with '--action export|printid'. Set the output file. (If not set, will use stdout)")
`Used with "--action export|printid". Set the output file. (If not set, will use stdout)`)
command.Flags().StringVarP(&baseUrl, "base-url", "", "",
`Manually set the base url of torrents list page. e.g.: "special.php", "adult.php", "torrents.php?cat=100"`)
cmd.AddEnumFlagP(command, &action, "action", "", ActionEnumFlag)
Expand Down Expand Up @@ -155,10 +154,10 @@ func batchdl(command *cobra.Command, args []string) error {
var includesList [][]string
var excludesList []string
for _, include := range includes {
includesList = append(includesList, strings.Split(include, ","))
includesList = append(includesList, util.SplitCsv(include))
}
if excludes != "" {
excludesList = strings.Split(excludes, ",")
excludesList = util.SplitCsv(excludes)
}
minTorrentSize, _ := util.RAMInBytes(minTorrentSizeStr)
maxTorrentSize, _ := util.RAMInBytes(maxTorrentSizeStr)
Expand Down Expand Up @@ -208,7 +207,7 @@ func batchdl(command *cobra.Command, args []string) error {
}
clientAddFixedTags = []string{client.GenerateTorrentTagFromSite(siteInstance.GetName())}
if addTags != "" {
clientAddFixedTags = append(clientAddFixedTags, strings.Split(addTags, ",")...)
clientAddFixedTags = append(clientAddFixedTags, util.SplitCsv(addTags)...)
}
} else if action == "export" || action == "printid" {
if exportFile != "" {
Expand Down
4 changes: 2 additions & 2 deletions cmd/common/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ var ClientTorrentSortFlag = &cmd.EnumFlag{
{"speed", ""},
{"state", ""},
{"time", ""},
{"activity-time", ""},
{"tracker", ""},
{"none", ""},
},
Expand Down Expand Up @@ -67,10 +68,8 @@ var pureFlags = []string{
"clients",
"delete-added",
"dense",
"do",
"dry-run",
"force",
"force-dangerous",
"force-local",
"fork",
"help",
Expand All @@ -83,6 +82,7 @@ var pureFlags = []string{
"no-hr",
"no-paid",
"parameters",
"partial",
"preserve",
"raw",
"rename-added",
Expand Down
5 changes: 2 additions & 3 deletions cmd/cookiecloud/cookiecloud.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
package cookiecloud

import (
"strings"

"github.com/spf13/cobra"

"github.com/sagan/ptool/cmd"
"github.com/sagan/ptool/config"
"github.com/sagan/ptool/util"
)

var Command = &cobra.Command{
Expand Down Expand Up @@ -43,7 +42,7 @@ func ParseProfile(profile string) []*config.CookiecloudConfigStruct {
cookiecloudProfiles = append(cookiecloudProfiles, profile)
}
} else {
names := strings.Split(profile, ",")
names := util.SplitCsv(profile)
for _, name := range names {
profile := config.GetCookiecloudConfig(name)
if profile != nil {
Expand Down
6 changes: 3 additions & 3 deletions cmd/cookiecloud/importsites/importsites.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import (
)

var (
doAction = false
force = false
noCheck = false
profile = ""
siteProxy = ""
Expand All @@ -39,7 +39,7 @@ Be aware that all existing comments in config file will be LOST when updating co
}

func init() {
command.Flags().BoolVarP(&doAction, "do", "", false,
command.Flags().BoolVarP(&force, "force", "", false,
"Do update the config file without confirm. Be aware that all existing comments in config file will be LOST")
command.Flags().BoolVarP(&noCheck, "no-check", "", false,
"Do not check the cookies validity before importing new sites")
Expand Down Expand Up @@ -160,7 +160,7 @@ func importsites(cmd *cobra.Command, args []string) error {
return sitename
}), ", "))
configFile := fmt.Sprintf("%s/%s", config.ConfigDir, config.ConfigFile)
if !doAction && !util.AskYesNoConfirm(
if !force && !util.AskYesNoConfirm(
fmt.Sprintf("Will update the config file (%s). Be aware that all existing comments will be LOST",
configFile)) {
return fmt.Errorf("abort")
Expand Down

0 comments on commit 8205a20

Please sign in to comment.