feat: improve support for pt

This commit is contained in:
Simon Ding
2024-08-01 19:12:14 +08:00
parent 35d299b60c
commit 408ff163ef
13 changed files with 450 additions and 80 deletions

View File

@@ -31,6 +31,8 @@ type History struct {
Size int `json:"size,omitempty"`
// DownloadClientID holds the value of the "download_client_id" field.
DownloadClientID int `json:"download_client_id,omitempty"`
// IndexerID holds the value of the "indexer_id" field.
IndexerID int `json:"indexer_id,omitempty"`
// Status holds the value of the "status" field.
Status history.Status `json:"status,omitempty"`
// Saved holds the value of the "saved" field.
@@ -43,7 +45,7 @@ func (*History) scanValues(columns []string) ([]any, error) {
values := make([]any, len(columns))
for i := range columns {
switch columns[i] {
case history.FieldID, history.FieldMediaID, history.FieldEpisodeID, history.FieldSize, history.FieldDownloadClientID:
case history.FieldID, history.FieldMediaID, history.FieldEpisodeID, history.FieldSize, history.FieldDownloadClientID, history.FieldIndexerID:
values[i] = new(sql.NullInt64)
case history.FieldSourceTitle, history.FieldTargetDir, history.FieldStatus, history.FieldSaved:
values[i] = new(sql.NullString)
@@ -112,6 +114,12 @@ func (h *History) assignValues(columns []string, values []any) error {
} else if value.Valid {
h.DownloadClientID = int(value.Int64)
}
case history.FieldIndexerID:
if value, ok := values[i].(*sql.NullInt64); !ok {
return fmt.Errorf("unexpected type %T for field indexer_id", values[i])
} else if value.Valid {
h.IndexerID = int(value.Int64)
}
case history.FieldStatus:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field status", values[i])
@@ -181,6 +189,9 @@ func (h *History) String() string {
builder.WriteString("download_client_id=")
builder.WriteString(fmt.Sprintf("%v", h.DownloadClientID))
builder.WriteString(", ")
builder.WriteString("indexer_id=")
builder.WriteString(fmt.Sprintf("%v", h.IndexerID))
builder.WriteString(", ")
builder.WriteString("status=")
builder.WriteString(fmt.Sprintf("%v", h.Status))
builder.WriteString(", ")

View File

@@ -27,6 +27,8 @@ const (
FieldSize = "size"
// FieldDownloadClientID holds the string denoting the download_client_id field in the database.
FieldDownloadClientID = "download_client_id"
// FieldIndexerID holds the string denoting the indexer_id field in the database.
FieldIndexerID = "indexer_id"
// FieldStatus holds the string denoting the status field in the database.
FieldStatus = "status"
// FieldSaved holds the string denoting the saved field in the database.
@@ -45,6 +47,7 @@ var Columns = []string{
FieldTargetDir,
FieldSize,
FieldDownloadClientID,
FieldIndexerID,
FieldStatus,
FieldSaved,
}
@@ -132,6 +135,11 @@ func ByDownloadClientID(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldDownloadClientID, opts...).ToFunc()
}
// ByIndexerID orders the results by the indexer_id field.
func ByIndexerID(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldIndexerID, opts...).ToFunc()
}
// ByStatus orders the results by the status field.
func ByStatus(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldStatus, opts...).ToFunc()

View File

@@ -89,6 +89,11 @@ func DownloadClientID(v int) predicate.History {
return predicate.History(sql.FieldEQ(FieldDownloadClientID, v))
}
// IndexerID applies equality check predicate on the "indexer_id" field. It's identical to IndexerIDEQ.
func IndexerID(v int) predicate.History {
return predicate.History(sql.FieldEQ(FieldIndexerID, v))
}
// Saved applies equality check predicate on the "saved" field. It's identical to SavedEQ.
func Saved(v string) predicate.History {
return predicate.History(sql.FieldEQ(FieldSaved, v))
@@ -444,6 +449,56 @@ func DownloadClientIDNotNil() predicate.History {
return predicate.History(sql.FieldNotNull(FieldDownloadClientID))
}
// IndexerIDEQ applies the EQ predicate on the "indexer_id" field.
func IndexerIDEQ(v int) predicate.History {
return predicate.History(sql.FieldEQ(FieldIndexerID, v))
}
// IndexerIDNEQ applies the NEQ predicate on the "indexer_id" field.
func IndexerIDNEQ(v int) predicate.History {
return predicate.History(sql.FieldNEQ(FieldIndexerID, v))
}
// IndexerIDIn applies the In predicate on the "indexer_id" field.
func IndexerIDIn(vs ...int) predicate.History {
return predicate.History(sql.FieldIn(FieldIndexerID, vs...))
}
// IndexerIDNotIn applies the NotIn predicate on the "indexer_id" field.
func IndexerIDNotIn(vs ...int) predicate.History {
return predicate.History(sql.FieldNotIn(FieldIndexerID, vs...))
}
// IndexerIDGT applies the GT predicate on the "indexer_id" field.
func IndexerIDGT(v int) predicate.History {
return predicate.History(sql.FieldGT(FieldIndexerID, v))
}
// IndexerIDGTE applies the GTE predicate on the "indexer_id" field.
func IndexerIDGTE(v int) predicate.History {
return predicate.History(sql.FieldGTE(FieldIndexerID, v))
}
// IndexerIDLT applies the LT predicate on the "indexer_id" field.
func IndexerIDLT(v int) predicate.History {
return predicate.History(sql.FieldLT(FieldIndexerID, v))
}
// IndexerIDLTE applies the LTE predicate on the "indexer_id" field.
func IndexerIDLTE(v int) predicate.History {
return predicate.History(sql.FieldLTE(FieldIndexerID, v))
}
// IndexerIDIsNil applies the IsNil predicate on the "indexer_id" field.
func IndexerIDIsNil() predicate.History {
return predicate.History(sql.FieldIsNull(FieldIndexerID))
}
// IndexerIDNotNil applies the NotNil predicate on the "indexer_id" field.
func IndexerIDNotNil() predicate.History {
return predicate.History(sql.FieldNotNull(FieldIndexerID))
}
// StatusEQ applies the EQ predicate on the "status" field.
func StatusEQ(v Status) predicate.History {
return predicate.History(sql.FieldEQ(FieldStatus, v))

View File

@@ -86,6 +86,20 @@ func (hc *HistoryCreate) SetNillableDownloadClientID(i *int) *HistoryCreate {
return hc
}
// SetIndexerID sets the "indexer_id" field.
func (hc *HistoryCreate) SetIndexerID(i int) *HistoryCreate {
hc.mutation.SetIndexerID(i)
return hc
}
// SetNillableIndexerID sets the "indexer_id" field if the given value is not nil.
func (hc *HistoryCreate) SetNillableIndexerID(i *int) *HistoryCreate {
if i != nil {
hc.SetIndexerID(*i)
}
return hc
}
// SetStatus sets the "status" field.
func (hc *HistoryCreate) SetStatus(h history.Status) *HistoryCreate {
hc.mutation.SetStatus(h)
@@ -226,6 +240,10 @@ func (hc *HistoryCreate) createSpec() (*History, *sqlgraph.CreateSpec) {
_spec.SetField(history.FieldDownloadClientID, field.TypeInt, value)
_node.DownloadClientID = value
}
if value, ok := hc.mutation.IndexerID(); ok {
_spec.SetField(history.FieldIndexerID, field.TypeInt, value)
_node.IndexerID = value
}
if value, ok := hc.mutation.Status(); ok {
_spec.SetField(history.FieldStatus, field.TypeEnum, value)
_node.Status = value

View File

@@ -166,6 +166,33 @@ func (hu *HistoryUpdate) ClearDownloadClientID() *HistoryUpdate {
return hu
}
// SetIndexerID sets the "indexer_id" field.
func (hu *HistoryUpdate) SetIndexerID(i int) *HistoryUpdate {
hu.mutation.ResetIndexerID()
hu.mutation.SetIndexerID(i)
return hu
}
// SetNillableIndexerID sets the "indexer_id" field if the given value is not nil.
func (hu *HistoryUpdate) SetNillableIndexerID(i *int) *HistoryUpdate {
if i != nil {
hu.SetIndexerID(*i)
}
return hu
}
// AddIndexerID adds i to the "indexer_id" field.
func (hu *HistoryUpdate) AddIndexerID(i int) *HistoryUpdate {
hu.mutation.AddIndexerID(i)
return hu
}
// ClearIndexerID clears the value of the "indexer_id" field.
func (hu *HistoryUpdate) ClearIndexerID() *HistoryUpdate {
hu.mutation.ClearIndexerID()
return hu
}
// SetStatus sets the "status" field.
func (hu *HistoryUpdate) SetStatus(h history.Status) *HistoryUpdate {
hu.mutation.SetStatus(h)
@@ -293,6 +320,15 @@ func (hu *HistoryUpdate) sqlSave(ctx context.Context) (n int, err error) {
if hu.mutation.DownloadClientIDCleared() {
_spec.ClearField(history.FieldDownloadClientID, field.TypeInt)
}
if value, ok := hu.mutation.IndexerID(); ok {
_spec.SetField(history.FieldIndexerID, field.TypeInt, value)
}
if value, ok := hu.mutation.AddedIndexerID(); ok {
_spec.AddField(history.FieldIndexerID, field.TypeInt, value)
}
if hu.mutation.IndexerIDCleared() {
_spec.ClearField(history.FieldIndexerID, field.TypeInt)
}
if value, ok := hu.mutation.Status(); ok {
_spec.SetField(history.FieldStatus, field.TypeEnum, value)
}
@@ -460,6 +496,33 @@ func (huo *HistoryUpdateOne) ClearDownloadClientID() *HistoryUpdateOne {
return huo
}
// SetIndexerID sets the "indexer_id" field.
func (huo *HistoryUpdateOne) SetIndexerID(i int) *HistoryUpdateOne {
huo.mutation.ResetIndexerID()
huo.mutation.SetIndexerID(i)
return huo
}
// SetNillableIndexerID sets the "indexer_id" field if the given value is not nil.
func (huo *HistoryUpdateOne) SetNillableIndexerID(i *int) *HistoryUpdateOne {
if i != nil {
huo.SetIndexerID(*i)
}
return huo
}
// AddIndexerID adds i to the "indexer_id" field.
func (huo *HistoryUpdateOne) AddIndexerID(i int) *HistoryUpdateOne {
huo.mutation.AddIndexerID(i)
return huo
}
// ClearIndexerID clears the value of the "indexer_id" field.
func (huo *HistoryUpdateOne) ClearIndexerID() *HistoryUpdateOne {
huo.mutation.ClearIndexerID()
return huo
}
// SetStatus sets the "status" field.
func (huo *HistoryUpdateOne) SetStatus(h history.Status) *HistoryUpdateOne {
huo.mutation.SetStatus(h)
@@ -617,6 +680,15 @@ func (huo *HistoryUpdateOne) sqlSave(ctx context.Context) (_node *History, err e
if huo.mutation.DownloadClientIDCleared() {
_spec.ClearField(history.FieldDownloadClientID, field.TypeInt)
}
if value, ok := huo.mutation.IndexerID(); ok {
_spec.SetField(history.FieldIndexerID, field.TypeInt, value)
}
if value, ok := huo.mutation.AddedIndexerID(); ok {
_spec.AddField(history.FieldIndexerID, field.TypeInt, value)
}
if huo.mutation.IndexerIDCleared() {
_spec.ClearField(history.FieldIndexerID, field.TypeInt)
}
if value, ok := huo.mutation.Status(); ok {
_spec.SetField(history.FieldStatus, field.TypeEnum, value)
}

View File

@@ -64,6 +64,7 @@ var (
{Name: "target_dir", Type: field.TypeString},
{Name: "size", Type: field.TypeInt, Default: 0},
{Name: "download_client_id", Type: field.TypeInt, Nullable: true},
{Name: "indexer_id", Type: field.TypeInt, Nullable: true},
{Name: "status", Type: field.TypeEnum, Enums: []string{"running", "success", "fail", "uploading"}},
{Name: "saved", Type: field.TypeString, Nullable: true},
}

View File

@@ -1719,6 +1719,8 @@ type HistoryMutation struct {
addsize *int
download_client_id *int
adddownload_client_id *int
indexer_id *int
addindexer_id *int
status *history.Status
saved *string
clearedFields map[string]struct{}
@@ -2185,6 +2187,76 @@ func (m *HistoryMutation) ResetDownloadClientID() {
delete(m.clearedFields, history.FieldDownloadClientID)
}
// SetIndexerID sets the "indexer_id" field.
func (m *HistoryMutation) SetIndexerID(i int) {
m.indexer_id = &i
m.addindexer_id = nil
}
// IndexerID returns the value of the "indexer_id" field in the mutation.
func (m *HistoryMutation) IndexerID() (r int, exists bool) {
v := m.indexer_id
if v == nil {
return
}
return *v, true
}
// OldIndexerID returns the old "indexer_id" field's value of the History entity.
// If the History object wasn't provided to the builder, the object is fetched from the database.
// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
func (m *HistoryMutation) OldIndexerID(ctx context.Context) (v int, err error) {
if !m.op.Is(OpUpdateOne) {
return v, errors.New("OldIndexerID is only allowed on UpdateOne operations")
}
if m.id == nil || m.oldValue == nil {
return v, errors.New("OldIndexerID requires an ID field in the mutation")
}
oldValue, err := m.oldValue(ctx)
if err != nil {
return v, fmt.Errorf("querying old value for OldIndexerID: %w", err)
}
return oldValue.IndexerID, nil
}
// AddIndexerID adds i to the "indexer_id" field.
func (m *HistoryMutation) AddIndexerID(i int) {
if m.addindexer_id != nil {
*m.addindexer_id += i
} else {
m.addindexer_id = &i
}
}
// AddedIndexerID returns the value that was added to the "indexer_id" field in this mutation.
func (m *HistoryMutation) AddedIndexerID() (r int, exists bool) {
v := m.addindexer_id
if v == nil {
return
}
return *v, true
}
// ClearIndexerID clears the value of the "indexer_id" field.
func (m *HistoryMutation) ClearIndexerID() {
m.indexer_id = nil
m.addindexer_id = nil
m.clearedFields[history.FieldIndexerID] = struct{}{}
}
// IndexerIDCleared returns if the "indexer_id" field was cleared in this mutation.
func (m *HistoryMutation) IndexerIDCleared() bool {
_, ok := m.clearedFields[history.FieldIndexerID]
return ok
}
// ResetIndexerID resets all changes to the "indexer_id" field.
func (m *HistoryMutation) ResetIndexerID() {
m.indexer_id = nil
m.addindexer_id = nil
delete(m.clearedFields, history.FieldIndexerID)
}
// SetStatus sets the "status" field.
func (m *HistoryMutation) SetStatus(h history.Status) {
m.status = &h
@@ -2304,7 +2376,7 @@ func (m *HistoryMutation) Type() string {
// order to get all numeric fields that were incremented/decremented, call
// AddedFields().
func (m *HistoryMutation) Fields() []string {
fields := make([]string, 0, 9)
fields := make([]string, 0, 10)
if m.media_id != nil {
fields = append(fields, history.FieldMediaID)
}
@@ -2326,6 +2398,9 @@ func (m *HistoryMutation) Fields() []string {
if m.download_client_id != nil {
fields = append(fields, history.FieldDownloadClientID)
}
if m.indexer_id != nil {
fields = append(fields, history.FieldIndexerID)
}
if m.status != nil {
fields = append(fields, history.FieldStatus)
}
@@ -2354,6 +2429,8 @@ func (m *HistoryMutation) Field(name string) (ent.Value, bool) {
return m.Size()
case history.FieldDownloadClientID:
return m.DownloadClientID()
case history.FieldIndexerID:
return m.IndexerID()
case history.FieldStatus:
return m.Status()
case history.FieldSaved:
@@ -2381,6 +2458,8 @@ func (m *HistoryMutation) OldField(ctx context.Context, name string) (ent.Value,
return m.OldSize(ctx)
case history.FieldDownloadClientID:
return m.OldDownloadClientID(ctx)
case history.FieldIndexerID:
return m.OldIndexerID(ctx)
case history.FieldStatus:
return m.OldStatus(ctx)
case history.FieldSaved:
@@ -2443,6 +2522,13 @@ func (m *HistoryMutation) SetField(name string, value ent.Value) error {
}
m.SetDownloadClientID(v)
return nil
case history.FieldIndexerID:
v, ok := value.(int)
if !ok {
return fmt.Errorf("unexpected type %T for field %s", value, name)
}
m.SetIndexerID(v)
return nil
case history.FieldStatus:
v, ok := value.(history.Status)
if !ok {
@@ -2477,6 +2563,9 @@ func (m *HistoryMutation) AddedFields() []string {
if m.adddownload_client_id != nil {
fields = append(fields, history.FieldDownloadClientID)
}
if m.addindexer_id != nil {
fields = append(fields, history.FieldIndexerID)
}
return fields
}
@@ -2493,6 +2582,8 @@ func (m *HistoryMutation) AddedField(name string) (ent.Value, bool) {
return m.AddedSize()
case history.FieldDownloadClientID:
return m.AddedDownloadClientID()
case history.FieldIndexerID:
return m.AddedIndexerID()
}
return nil, false
}
@@ -2530,6 +2621,13 @@ func (m *HistoryMutation) AddField(name string, value ent.Value) error {
}
m.AddDownloadClientID(v)
return nil
case history.FieldIndexerID:
v, ok := value.(int)
if !ok {
return fmt.Errorf("unexpected type %T for field %s", value, name)
}
m.AddIndexerID(v)
return nil
}
return fmt.Errorf("unknown History numeric field %s", name)
}
@@ -2544,6 +2642,9 @@ func (m *HistoryMutation) ClearedFields() []string {
if m.FieldCleared(history.FieldDownloadClientID) {
fields = append(fields, history.FieldDownloadClientID)
}
if m.FieldCleared(history.FieldIndexerID) {
fields = append(fields, history.FieldIndexerID)
}
if m.FieldCleared(history.FieldSaved) {
fields = append(fields, history.FieldSaved)
}
@@ -2567,6 +2668,9 @@ func (m *HistoryMutation) ClearField(name string) error {
case history.FieldDownloadClientID:
m.ClearDownloadClientID()
return nil
case history.FieldIndexerID:
m.ClearIndexerID()
return nil
case history.FieldSaved:
m.ClearSaved()
return nil
@@ -2599,6 +2703,9 @@ func (m *HistoryMutation) ResetField(name string) error {
case history.FieldDownloadClientID:
m.ResetDownloadClientID()
return nil
case history.FieldIndexerID:
m.ResetIndexerID()
return nil
case history.FieldStatus:
m.ResetStatus()
return nil

View File

@@ -20,6 +20,7 @@ func (History) Fields() []ent.Field {
field.String("target_dir"),
field.Int("size").Default(0),
field.Int("download_client_id").Optional(),
field.Int("indexer_id").Optional(),
field.Enum("status").Values("running", "success", "fail", "uploading"),
field.String("saved").Optional(),
}

View File

@@ -6,6 +6,7 @@ import (
"io"
"net/http"
"net/url"
"polaris/db"
"polaris/log"
"strconv"
"time"
@@ -71,17 +72,22 @@ func (i *Item) GetAttr(key string) string {
}
return ""
}
func (r *Response) ToResults() []Result {
func (r *Response) ToResults(indexer *db.TorznabInfo) []Result {
var res []Result
for _, item := range r.Channel.Item {
r := Result{
Name: item.Title,
Link: item.Link,
Size: mustAtoI(item.Size),
Seeders: mustAtoI(item.GetAttr("seeders")),
Peers: mustAtoI(item.GetAttr("peers")),
Category: mustAtoI(item.GetAttr("category")),
Source: r.Channel.Title,
Name: item.Title,
Link: item.Link,
Size: mustAtoI(item.Size),
Seeders: mustAtoI(item.GetAttr("seeders")),
Peers: mustAtoI(item.GetAttr("peers")),
Category: mustAtoI(item.GetAttr("category")),
DownloadVolumeFactor: tryParseFloat(item.GetAttr("downloadvolumefactor")),
UploadVolumeFactor: tryParseFloat(item.GetAttr("uploadvolumefactor")),
Source: indexer.Name,
IndexerId: indexer.ID,
Priority: indexer.Priority,
IsPrivate: item.Type == "private",
}
res = append(res, r)
}
@@ -96,11 +102,21 @@ func mustAtoI(key string) int {
}
return i
}
func Search(torznabUrl, api, keyWord string) ([]Result, error) {
func tryParseFloat(s string) float32 {
r, err := strconv.ParseFloat(s, 32)
if err != nil {
log.Warnf("parse float error: %v", err)
return 0
}
return float32(r)
}
func Search(indexer *db.TorznabInfo, api, keyWord string) ([]Result, error) {
ctx, cancel := context.WithTimeout(context.TODO(), 10*time.Second)
defer cancel()
req, err := http.NewRequestWithContext(ctx, http.MethodGet, torznabUrl, nil)
req, err := http.NewRequestWithContext(ctx, http.MethodGet, indexer.URL, nil)
if err != nil {
return nil, errors.Wrap(err, "new request")
}
@@ -124,15 +140,20 @@ func Search(torznabUrl, api, keyWord string) ([]Result, error) {
if err != nil {
return nil, errors.Wrap(err, "json unmarshal")
}
return res.ToResults(), nil
return res.ToResults(indexer), nil
}
type Result struct {
Name string
Link string
Size int
Seeders int
Peers int
Category int
Source string
Name string `json:"name"`
Link string `json:"link"`
Size int `json:"size"`
Seeders int `json:"seeders"`
Peers int `json:"peers"`
Category int `json:"category"`
Source string `json:"source"`
DownloadVolumeFactor float32 `json:"download_volume_factor"`
UploadVolumeFactor float32 `json:"upload_volume_factor"`
IndexerId int `json:"indexer_id"`
Priority int `json:"priority"`
IsPrivate bool `json:"is_private"`
}

View File

@@ -77,7 +77,7 @@ func SearchEpisode(db1 *db.Client, seriesId, seasonNum, episodeNum int, checkRes
if len(filtered) == 0 {
return nil, errors.New("no resource found")
}
filtered = dedup(filtered)
return filtered, nil
}
@@ -117,6 +117,7 @@ func SearchMovie(db1 *db.Client, movieId int, checkResolution bool) ([]torznab.R
if len(filtered) == 0 {
return nil, errors.New("no resource found")
}
filtered = dedup(filtered)
return filtered, nil
@@ -134,7 +135,7 @@ func searchWithTorznab(db *db.Client, q string) []torznab.Result {
go func() {
log.Debugf("search torznab %v with %v", tor.Name, q)
defer wg.Done()
resp, err := torznab.Search(tor.URL, tor.ApiKey, q)
resp, err := torznab.Search(tor, tor.ApiKey, q)
if err != nil {
log.Errorf("search %s error: %v", tor.Name, err)
return
@@ -152,11 +153,54 @@ func searchWithTorznab(db *db.Client, q string) []torznab.Result {
res = append(res, result...)
}
sort.Slice(res, func(i, j int) bool {
//res = dedup(res)
sort.SliceStable(res, func(i, j int) bool { //先按做种人数排序
var s1 = res[i]
var s2 = res[j]
return s1.Seeders > s2.Seeders
})
sort.SliceStable(res, func(i, j int) bool { //再按优先级排序,优先级高的种子排前面
var s1 = res[i]
var s2 = res[j]
return s1.Priority > s2.Priority
})
//pt资源中同一indexer内部优先下载free的资源
sort.SliceStable(res, func(i, j int) bool {
var s1 = res[i]
var s2 = res[j]
if s1.IndexerId == s2.IndexerId && s1.IsPrivate {
return s1.DownloadVolumeFactor < s2.DownloadVolumeFactor
}
return false
})
//同一indexer内部如果下载消耗一样则优先下载上传奖励较多的
sort.SliceStable(res, func(i, j int) bool {
var s1 = res[i]
var s2 = res[j]
if s1.IndexerId == s2.IndexerId && s1.IsPrivate && s1.DownloadVolumeFactor == s2.DownloadVolumeFactor{
return s1.UploadVolumeFactor > s2.UploadVolumeFactor
}
return false
})
return res
}
func dedup(list []torznab.Result) []torznab.Result {
var res = make([]torznab.Result, 0, len(list))
seen := make(map[string]bool, 0)
for _, r := range list {
key := fmt.Sprintf("%s%s%d%d", r.Name, r.Source, r.Seeders,r.Peers)
if seen[key] {
continue
}
seen[key] = true
res = append(res, r)
}
return res
}

View File

@@ -62,6 +62,7 @@ func (s *Server) downloadSeasonPackage(r1 torznab.Result, seriesId, seasonNum in
Size: r1.Size,
Saved: torrent.Save(),
DownloadClientID: dlClient.ID,
IndexerID: r1.IndexerId,
})
if err != nil {
return nil, errors.Wrap(err, "save record")
@@ -110,6 +111,7 @@ func (s *Server) downloadEpisodeTorrent(r1 torznab.Result, seriesId, seasonNum,
Size: r1.Size,
Saved: torrent.Save(),
DownloadClientID: dlc.ID,
IndexerID: r1.IndexerId,
})
if err != nil {
return nil, errors.Wrap(err, "save record")
@@ -165,7 +167,7 @@ func (s *Server) SearchAvailableTorrents(c *gin.Context) (interface{}, error) {
res, err = core.SearchEpisode(s.db, in.ID, in.Season, in.Episode, false)
if err != nil {
if err.Error() == "no resource found" {
return []TorznabSearchResult{}, nil
return gin.H{}, nil
}
return nil, errors.Wrap(err, "search episode")
}
@@ -176,22 +178,12 @@ func (s *Server) SearchAvailableTorrents(c *gin.Context) (interface{}, error) {
res, err = core.SearchMovie(s.db, in.ID, false)
if err != nil {
if err.Error() == "no resource found" {
return []TorznabSearchResult{}, nil
return gin.H{}, nil
}
return nil, err
}
}
var searchResults []TorznabSearchResult
for _, r := range res {
searchResults = append(searchResults, TorznabSearchResult{
Name: r.Name,
Size: r.Size,
Seeders: r.Seeders,
Peers: r.Peers,
Link: r.Link,
})
}
return searchResults, nil
return res, nil
}
func (s *Server) SearchTvAndDownload(c *gin.Context) (interface{}, error) {
@@ -223,19 +215,11 @@ func (s *Server) SearchTvAndDownload(c *gin.Context) (interface{}, error) {
}, nil
}
type TorznabSearchResult struct {
Name string `json:"name"`
Size int `json:"size"`
Link string `json:"link"`
Seeders int `json:"seeders"`
Peers int `json:"peers"`
Source string `json:"source"`
}
type downloadTorrentIn struct {
MediaID int `json:"id" binding:"required"`
Season int `json:"season"`
Episode int `json:"episode"`
TorznabSearchResult
torznab.Result
}
func (s *Server) DownloadTorrent(c *gin.Context) (interface{}, error) {
@@ -263,7 +247,7 @@ func (s *Server) DownloadTorrent(c *gin.Context) (interface{}, error) {
if name == "" {
name = fmt.Sprintf("%v S%02dE%02d", m.OriginalName, in.Season, in.Episode)
}
res := torznab.Result{Name: name, Link: in.Link, Size: in.Size}
res := torznab.Result{Name: name, Link: in.Link, Size: in.Size, IndexerId: in.IndexerId}
return s.downloadEpisodeTorrent(res, in.MediaID, in.Season, in.Episode)
} else {
//movie
@@ -292,6 +276,7 @@ func (s *Server) DownloadTorrent(c *gin.Context) (interface{}, error) {
Size: in.Size,
Saved: torrent.Save(),
DownloadClientID: dlc.ID,
IndexerID: in.IndexerId,
})
if err != nil {
log.Errorf("save history error: %v", err)

View File

@@ -98,7 +98,7 @@ class SeriesDetails {
mediaType = json["media_type"];
storage = Storage.fromJson(json["storage"]);
targetDir = json["target_dir"];
downloadHistoryEpisodes = json["download_history_episodes"]??false;
downloadHistoryEpisodes = json["download_history_episodes"] ?? false;
if (json['episodes'] != null) {
episodes = <Episodes>[];
json['episodes'].forEach((v) {
@@ -195,13 +195,27 @@ class MediaTorrentResource extends AutoDisposeFamilyAsyncNotifier<
}
class TorrentResource {
TorrentResource({this.name, this.size, this.seeders, this.peers, this.link});
TorrentResource(
{this.name,
this.size,
this.seeders,
this.peers,
this.link,
this.source,
this.indexerId,
this.downloadFactor,
this.uploadFactor, this.isPrivate});
String? name;
int? size;
int? seeders;
int? peers;
String? link;
String? source;
int? indexerId;
double? downloadFactor;
double? uploadFactor;
bool? isPrivate;
factory TorrentResource.fromJson(Map<String, dynamic> json) {
return TorrentResource(
@@ -209,13 +223,20 @@ class TorrentResource {
size: json["size"],
seeders: json["seeders"],
peers: json["peers"],
link: json["link"]);
link: json["link"],
source: json["source"],
indexerId: json["indexer_id"],
isPrivate: json["is_private"]??false,
downloadFactor: json["download_volume_factor"],
uploadFactor: json["upload_volume_factor"]);
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
data['name'] = name;
data['size'] = size;
data["link"] = link;
data["indexer_id"] = indexerId;
data["source"] = source;
return data;
}
}

View File

@@ -71,7 +71,8 @@ class _TvDetailsPageState extends ConsumerState<TvDetailsPage> {
.read(mediaDetailsProvider(widget.seriesId)
.notifier)
.searchAndDownload(widget.seriesId,
ep.seasonNumber!, ep.episodeNumber!).then((v) => showSnakeBar("开始下载: $v"));
ep.seasonNumber!, ep.episodeNumber!)
.then((v) => showSnakeBar("开始下载: $v"));
showLoadingWithFuture(f);
},
icon: const Icon(Icons.download)),
@@ -118,7 +119,8 @@ class _TvDetailsPageState extends ConsumerState<TvDetailsPage> {
final f = ref
.read(mediaDetailsProvider(widget.seriesId)
.notifier)
.searchAndDownload(widget.seriesId, k, 0).then((v) => showSnakeBar("开始下载: $v"));
.searchAndDownload(widget.seriesId, k, 0)
.then((v) => showSnakeBar("开始下载: $v"));
showLoadingWithFuture(f);
},
icon: const Icon(Icons.download)),
@@ -165,47 +167,71 @@ class _TvDetailsPageState extends ConsumerState<TvDetailsPage> {
//title: Text("资源"),
content: SelectionArea(
child: SizedBox(
width: MediaQuery.of(context).size.width*0.7,
height: MediaQuery.of(context).size.height*0.6,
width: MediaQuery.of(context).size.width * 0.7,
height: MediaQuery.of(context).size.height * 0.6,
child: torrents.when(
data: (v) {
bool hasPrivate = false;
for (final item in v) {
if (item.isPrivate == true) {
hasPrivate = true;
}
}
final columns = [
const DataColumn(label: Text("名称")),
const DataColumn(label: Text("大小")),
const DataColumn(label: Text("S/P")),
const DataColumn(label: Text("来源")),
];
if (hasPrivate) {
columns.add(const DataColumn(label: Text("消耗")));
}
columns.add(const DataColumn(label: Text("下载")));
return SingleChildScrollView(
child: DataTable(
dataTextStyle:
const TextStyle(fontSize: 12),
columns: const [
DataColumn(label: Text("名称")),
DataColumn(label: Text("大小")),
DataColumn(label: Text("seeders")),
DataColumn(label: Text("peers")),
DataColumn(label: Text("操作"))
],
dataTextStyle: const TextStyle(fontSize: 12),
columns: columns,
rows: List.generate(v.length, (i) {
final torrent = v[i];
return DataRow(cells: [
final rows = [
DataCell(Text("${torrent.name}")),
DataCell(Text(
"${torrent.size?.readableFileSize()}")),
DataCell(Text("${torrent.seeders}")),
DataCell(Text("${torrent.peers}")),
DataCell(IconButton(
icon: const Icon(Icons.download),
onPressed: () async {
var f = ref
.read(mediaTorrentsDataProvider((
mediaId: id,
seasonNumber: season,
episodeNumber: episode
)).notifier)
.download(torrent).then((v) => showSnakeBar("开始下载:${torrent.name}"));
showLoadingWithFuture(f);
},
))
]);
DataCell(Text(
"${torrent.seeders}/${torrent.peers}")),
DataCell(Text(torrent.source ?? "-")),
];
if (hasPrivate) {
rows.add(DataCell(Text(torrent.isPrivate == true
? "${torrent.downloadFactor}dl/${torrent.uploadFactor}up"
: "-")));
}
rows.add(DataCell(IconButton(
icon: const Icon(Icons.download),
onPressed: () async {
var f = ref
.read(mediaTorrentsDataProvider((
mediaId: id,
seasonNumber: season,
episodeNumber: episode
)).notifier)
.download(torrent)
.then((v) =>
showSnakeBar("开始下载:${torrent.name}"));
showLoadingWithFuture(f);
},
)));
return DataRow(cells: rows);
})));
},
error: (err, trace) {
return Text("$err");
return "$err".contains("no resource found")
? const Center(
child: Text("没有资源"),
)
: Text("$err");
},
loading: () => const MyProgressIndicator()),
),