mirror of
https://github.com/Prowlarr/Prowlarr.git
synced 2025-09-17 17:14:18 +02:00
New: Load more (page) results on Search UI
This commit is contained in:
@@ -37,7 +37,10 @@ class SearchFooter extends Component {
|
||||
searchingReleases: false,
|
||||
searchQuery: defaultSearchQuery || '',
|
||||
searchIndexerIds: defaultIndexerIds,
|
||||
searchCategories: defaultCategories
|
||||
searchCategories: defaultCategories,
|
||||
searchLimit: 100,
|
||||
searchOffset: 0,
|
||||
newSearch: true
|
||||
};
|
||||
}
|
||||
|
||||
@@ -115,11 +118,28 @@ class SearchFooter extends Component {
|
||||
};
|
||||
|
||||
onSearchPress = () => {
|
||||
this.props.onSearchPress(this.state.searchQuery, this.state.searchIndexerIds, this.state.searchCategories, this.state.searchType);
|
||||
|
||||
const {
|
||||
searchLimit,
|
||||
searchOffset,
|
||||
searchQuery,
|
||||
searchIndexerIds,
|
||||
searchCategories,
|
||||
searchType
|
||||
} = this.state;
|
||||
|
||||
this.props.onSearchPress(searchQuery, searchIndexerIds, searchCategories, searchType, searchLimit, searchOffset);
|
||||
|
||||
this.setState({ searchOffset: searchOffset + 100, newSearch: false });
|
||||
};
|
||||
|
||||
onSearchInputChange = ({ value }) => {
|
||||
this.setState({ searchQuery: value });
|
||||
this.setState({ searchQuery: value, newSearch: true, searchOffset: 0 });
|
||||
};
|
||||
|
||||
onInputChange = ({ name, value }) => {
|
||||
this.props.onInputChange({ name, value });
|
||||
this.setState({ newSearch: true, searchOffset: 0 });
|
||||
};
|
||||
|
||||
//
|
||||
@@ -141,6 +161,7 @@ class SearchFooter extends Component {
|
||||
searchQuery,
|
||||
searchIndexerIds,
|
||||
searchCategories,
|
||||
newSearch,
|
||||
isQueryParameterModalOpen,
|
||||
queryModalOptions,
|
||||
searchType
|
||||
@@ -206,7 +227,7 @@ class SearchFooter extends Component {
|
||||
name='searchIndexerIds'
|
||||
value={searchIndexerIds}
|
||||
isDisabled={isFetching}
|
||||
onChange={onInputChange}
|
||||
onChange={this.onInputChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -220,7 +241,7 @@ class SearchFooter extends Component {
|
||||
name='searchCategories'
|
||||
value={searchCategories}
|
||||
isDisabled={isFetching}
|
||||
onChange={onInputChange}
|
||||
onChange={this.onInputChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -253,7 +274,7 @@ class SearchFooter extends Component {
|
||||
isDisabled={isFetching || !hasIndexers}
|
||||
onPress={this.onSearchPress}
|
||||
>
|
||||
{translate('Search')}
|
||||
{newSearch ? translate('Search') : translate('More')}
|
||||
</SpinnerButton>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -196,8 +196,8 @@ class SearchIndex extends Component {
|
||||
this.setState({ jumpToCharacter });
|
||||
};
|
||||
|
||||
onSearchPress = (query, indexerIds, categories, type) => {
|
||||
this.props.onSearchPress({ query, indexerIds, categories, type });
|
||||
onSearchPress = (query, indexerIds, categories, type, limit, offset) => {
|
||||
this.props.onSearchPress({ query, indexerIds, categories, type, limit, offset });
|
||||
};
|
||||
|
||||
onBulkGrabPress = () => {
|
||||
|
117
src/Prowlarr.Api.V1/Search/ReleaseResource.cs
Normal file
117
src/Prowlarr.Api.V1/Search/ReleaseResource.cs
Normal file
@@ -0,0 +1,117 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NzbDrone.Core.Indexers;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using Prowlarr.Http.REST;
|
||||
|
||||
namespace Prowlarr.Api.V1.Search
|
||||
{
|
||||
public class ReleaseResource : RestResource
|
||||
{
|
||||
public string Guid { get; set; }
|
||||
public int Age { get; set; }
|
||||
public double AgeHours { get; set; }
|
||||
public double AgeMinutes { get; set; }
|
||||
public long Size { get; set; }
|
||||
public int? Files { get; set; }
|
||||
public int? Grabs { get; set; }
|
||||
public int IndexerId { get; set; }
|
||||
public string Indexer { get; set; }
|
||||
public string SubGroup { get; set; }
|
||||
public string ReleaseHash { get; set; }
|
||||
public string Title { get; set; }
|
||||
public bool Approved { get; set; }
|
||||
public int ImdbId { get; set; }
|
||||
public DateTime PublishDate { get; set; }
|
||||
public string CommentUrl { get; set; }
|
||||
public string DownloadUrl { get; set; }
|
||||
public string InfoUrl { get; set; }
|
||||
public string PosterUrl { get; set; }
|
||||
public IEnumerable<string> IndexerFlags { get; set; }
|
||||
public ICollection<IndexerCategory> Categories { get; set; }
|
||||
|
||||
public string MagnetUrl { get; set; }
|
||||
public string InfoHash { get; set; }
|
||||
public int? Seeders { get; set; }
|
||||
public int? Leechers { get; set; }
|
||||
public DownloadProtocol Protocol { get; set; }
|
||||
}
|
||||
|
||||
public static class ReleaseResourceMapper
|
||||
{
|
||||
public static ReleaseResource ToResource(this ReleaseInfo model)
|
||||
{
|
||||
var releaseInfo = model;
|
||||
var torrentInfo = (model as TorrentInfo) ?? new TorrentInfo();
|
||||
var indexerFlags = torrentInfo.IndexerFlags.Select(f => f.Name);
|
||||
|
||||
// TODO: Clean this mess up. don't mix data from multiple classes, use sub-resources instead? (Got a huge Deja Vu, didn't we talk about this already once?)
|
||||
return new ReleaseResource
|
||||
{
|
||||
Guid = releaseInfo.Guid,
|
||||
|
||||
//QualityWeight
|
||||
Age = releaseInfo.Age,
|
||||
AgeHours = releaseInfo.AgeHours,
|
||||
AgeMinutes = releaseInfo.AgeMinutes,
|
||||
Size = releaseInfo.Size ?? 0,
|
||||
Files = releaseInfo.Files,
|
||||
Grabs = releaseInfo.Grabs,
|
||||
IndexerId = releaseInfo.IndexerId,
|
||||
Indexer = releaseInfo.Indexer,
|
||||
Title = releaseInfo.Title,
|
||||
ImdbId = releaseInfo.ImdbId,
|
||||
PublishDate = releaseInfo.PublishDate,
|
||||
CommentUrl = releaseInfo.CommentUrl,
|
||||
DownloadUrl = releaseInfo.DownloadUrl,
|
||||
InfoUrl = releaseInfo.InfoUrl,
|
||||
PosterUrl = releaseInfo.PosterUrl,
|
||||
Categories = releaseInfo.Categories,
|
||||
|
||||
//ReleaseWeight
|
||||
MagnetUrl = torrentInfo.MagnetUrl,
|
||||
InfoHash = torrentInfo.InfoHash,
|
||||
Seeders = torrentInfo.Seeders,
|
||||
Leechers = (torrentInfo.Peers.HasValue && torrentInfo.Seeders.HasValue) ? (torrentInfo.Peers.Value - torrentInfo.Seeders.Value) : (int?)null,
|
||||
Protocol = releaseInfo.DownloadProtocol,
|
||||
IndexerFlags = indexerFlags
|
||||
};
|
||||
}
|
||||
|
||||
public static ReleaseInfo ToModel(this ReleaseResource resource)
|
||||
{
|
||||
ReleaseInfo model;
|
||||
|
||||
if (resource.Protocol == DownloadProtocol.Torrent)
|
||||
{
|
||||
model = new TorrentInfo
|
||||
{
|
||||
MagnetUrl = resource.MagnetUrl,
|
||||
InfoHash = resource.InfoHash,
|
||||
Seeders = resource.Seeders,
|
||||
Peers = (resource.Seeders.HasValue && resource.Leechers.HasValue) ? (resource.Seeders + resource.Leechers) : null
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
model = new ReleaseInfo();
|
||||
}
|
||||
|
||||
model.Guid = resource.Guid;
|
||||
model.Title = resource.Title;
|
||||
model.Size = resource.Size;
|
||||
model.DownloadUrl = resource.DownloadUrl;
|
||||
model.InfoUrl = resource.InfoUrl;
|
||||
model.PosterUrl = resource.PosterUrl;
|
||||
model.CommentUrl = resource.CommentUrl;
|
||||
model.IndexerId = resource.IndexerId;
|
||||
model.Indexer = resource.Indexer;
|
||||
model.DownloadProtocol = resource.Protocol;
|
||||
model.ImdbId = resource.ImdbId;
|
||||
model.PublishDate = resource.PublishDate.ToUniversalTime();
|
||||
|
||||
return model;
|
||||
}
|
||||
}
|
||||
}
|
@@ -22,7 +22,7 @@ using Prowlarr.Http.REST;
|
||||
namespace Prowlarr.Api.V1.Search
|
||||
{
|
||||
[V1ApiController]
|
||||
public class SearchController : RestController<SearchResource>
|
||||
public class SearchController : RestController<ReleaseResource>
|
||||
{
|
||||
private readonly ISearchForNzb _nzbSearhService;
|
||||
private readonly IDownloadService _downloadService;
|
||||
@@ -46,13 +46,13 @@ namespace Prowlarr.Api.V1.Search
|
||||
_remoteReleaseCache = cacheManager.GetCache<ReleaseInfo>(GetType(), "remoteReleases");
|
||||
}
|
||||
|
||||
public override SearchResource GetResourceById(int id)
|
||||
public override ReleaseResource GetResourceById(int id)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public ActionResult<SearchResource> GrabRelease(SearchResource release)
|
||||
public ActionResult<ReleaseResource> GrabRelease(ReleaseResource release)
|
||||
{
|
||||
ValidateResource(release);
|
||||
|
||||
@@ -76,7 +76,7 @@ namespace Prowlarr.Api.V1.Search
|
||||
}
|
||||
|
||||
[HttpPost("bulk")]
|
||||
public ActionResult<SearchResource> GrabReleases(List<SearchResource> releases)
|
||||
public ActionResult<ReleaseResource> GrabReleases(List<ReleaseResource> releases)
|
||||
{
|
||||
var source = UserAgentParser.ParseSource(Request.Headers["User-Agent"]);
|
||||
var host = Request.GetHostName();
|
||||
@@ -108,35 +108,30 @@ namespace Prowlarr.Api.V1.Search
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public Task<List<SearchResource>> GetAll(string query, [FromQuery] List<int> indexerIds, [FromQuery] List<int> categories, [FromQuery] string type = "search")
|
||||
public Task<List<ReleaseResource>> GetAll([FromQuery] SearchResource payload)
|
||||
{
|
||||
if (indexerIds.Any())
|
||||
{
|
||||
return GetSearchReleases(query, type, indexerIds, categories);
|
||||
}
|
||||
else
|
||||
{
|
||||
return GetSearchReleases(query, type, null, categories);
|
||||
}
|
||||
return GetSearchReleases(payload);
|
||||
}
|
||||
|
||||
private async Task<List<SearchResource>> GetSearchReleases(string query, string type, List<int> indexerIds, List<int> categories)
|
||||
private async Task<List<ReleaseResource>> GetSearchReleases([FromQuery] SearchResource payload)
|
||||
{
|
||||
try
|
||||
{
|
||||
var request = new NewznabRequest
|
||||
{
|
||||
q = query,
|
||||
t = type,
|
||||
q = payload.Query,
|
||||
t = payload.Type,
|
||||
source = "Prowlarr",
|
||||
cat = string.Join(",", categories),
|
||||
cat = string.Join(",", payload.Categories),
|
||||
server = Request.GetServerUrl(),
|
||||
host = Request.GetHostName()
|
||||
host = Request.GetHostName(),
|
||||
limit = payload.Limit,
|
||||
offset = payload.Offset
|
||||
};
|
||||
|
||||
request.QueryToParams();
|
||||
|
||||
var result = await _nzbSearhService.Search(request, indexerIds, true);
|
||||
var result = await _nzbSearhService.Search(request, payload.IndexerIds, true);
|
||||
var decisions = result.Releases;
|
||||
|
||||
return MapDecisions(decisions, Request.GetServerUrl());
|
||||
@@ -150,12 +145,12 @@ namespace Prowlarr.Api.V1.Search
|
||||
_logger.Error(ex, "Search failed: " + ex.Message);
|
||||
}
|
||||
|
||||
return new List<SearchResource>();
|
||||
return new List<ReleaseResource>();
|
||||
}
|
||||
|
||||
protected virtual List<SearchResource> MapDecisions(IEnumerable<ReleaseInfo> releases, string serverUrl)
|
||||
protected virtual List<ReleaseResource> MapDecisions(IEnumerable<ReleaseInfo> releases, string serverUrl)
|
||||
{
|
||||
var result = new List<SearchResource>();
|
||||
var result = new List<ReleaseResource>();
|
||||
|
||||
foreach (var downloadDecision in releases)
|
||||
{
|
||||
@@ -173,7 +168,7 @@ namespace Prowlarr.Api.V1.Search
|
||||
return result;
|
||||
}
|
||||
|
||||
private string GetCacheKey(SearchResource resource)
|
||||
private string GetCacheKey(ReleaseResource resource)
|
||||
{
|
||||
return string.Concat(resource.IndexerId, "_", resource.Guid);
|
||||
}
|
||||
|
@@ -1,117 +1,24 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NzbDrone.Core.Indexers;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using Prowlarr.Http.REST;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Prowlarr.Api.V1.Search
|
||||
{
|
||||
public class SearchResource : RestResource
|
||||
public class SearchResource
|
||||
{
|
||||
public string Guid { get; set; }
|
||||
public int Age { get; set; }
|
||||
public double AgeHours { get; set; }
|
||||
public double AgeMinutes { get; set; }
|
||||
public long Size { get; set; }
|
||||
public int? Files { get; set; }
|
||||
public int? Grabs { get; set; }
|
||||
public int IndexerId { get; set; }
|
||||
public string Indexer { get; set; }
|
||||
public string SubGroup { get; set; }
|
||||
public string ReleaseHash { get; set; }
|
||||
public string Title { get; set; }
|
||||
public bool Approved { get; set; }
|
||||
public int ImdbId { get; set; }
|
||||
public DateTime PublishDate { get; set; }
|
||||
public string CommentUrl { get; set; }
|
||||
public string DownloadUrl { get; set; }
|
||||
public string InfoUrl { get; set; }
|
||||
public string PosterUrl { get; set; }
|
||||
public IEnumerable<string> IndexerFlags { get; set; }
|
||||
public ICollection<IndexerCategory> Categories { get; set; }
|
||||
|
||||
public string MagnetUrl { get; set; }
|
||||
public string InfoHash { get; set; }
|
||||
public int? Seeders { get; set; }
|
||||
public int? Leechers { get; set; }
|
||||
public DownloadProtocol Protocol { get; set; }
|
||||
}
|
||||
|
||||
public static class ReleaseResourceMapper
|
||||
{
|
||||
public static SearchResource ToResource(this ReleaseInfo model)
|
||||
public SearchResource()
|
||||
{
|
||||
var releaseInfo = model;
|
||||
var torrentInfo = (model as TorrentInfo) ?? new TorrentInfo();
|
||||
var indexerFlags = torrentInfo.IndexerFlags.Select(f => f.Name);
|
||||
|
||||
// TODO: Clean this mess up. don't mix data from multiple classes, use sub-resources instead? (Got a huge Deja Vu, didn't we talk about this already once?)
|
||||
return new SearchResource
|
||||
{
|
||||
Guid = releaseInfo.Guid,
|
||||
|
||||
//QualityWeight
|
||||
Age = releaseInfo.Age,
|
||||
AgeHours = releaseInfo.AgeHours,
|
||||
AgeMinutes = releaseInfo.AgeMinutes,
|
||||
Size = releaseInfo.Size ?? 0,
|
||||
Files = releaseInfo.Files,
|
||||
Grabs = releaseInfo.Grabs,
|
||||
IndexerId = releaseInfo.IndexerId,
|
||||
Indexer = releaseInfo.Indexer,
|
||||
Title = releaseInfo.Title,
|
||||
ImdbId = releaseInfo.ImdbId,
|
||||
PublishDate = releaseInfo.PublishDate,
|
||||
CommentUrl = releaseInfo.CommentUrl,
|
||||
DownloadUrl = releaseInfo.DownloadUrl,
|
||||
InfoUrl = releaseInfo.InfoUrl,
|
||||
PosterUrl = releaseInfo.PosterUrl,
|
||||
Categories = releaseInfo.Categories,
|
||||
|
||||
//ReleaseWeight
|
||||
MagnetUrl = torrentInfo.MagnetUrl,
|
||||
InfoHash = torrentInfo.InfoHash,
|
||||
Seeders = torrentInfo.Seeders,
|
||||
Leechers = (torrentInfo.Peers.HasValue && torrentInfo.Seeders.HasValue) ? (torrentInfo.Peers.Value - torrentInfo.Seeders.Value) : (int?)null,
|
||||
Protocol = releaseInfo.DownloadProtocol,
|
||||
IndexerFlags = indexerFlags
|
||||
};
|
||||
Type = "search";
|
||||
Categories = new List<int>();
|
||||
}
|
||||
|
||||
public static ReleaseInfo ToModel(this SearchResource resource)
|
||||
{
|
||||
ReleaseInfo model;
|
||||
|
||||
if (resource.Protocol == DownloadProtocol.Torrent)
|
||||
{
|
||||
model = new TorrentInfo
|
||||
{
|
||||
MagnetUrl = resource.MagnetUrl,
|
||||
InfoHash = resource.InfoHash,
|
||||
Seeders = resource.Seeders,
|
||||
Peers = (resource.Seeders.HasValue && resource.Leechers.HasValue) ? (resource.Seeders + resource.Leechers) : null
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
model = new ReleaseInfo();
|
||||
}
|
||||
|
||||
model.Guid = resource.Guid;
|
||||
model.Title = resource.Title;
|
||||
model.Size = resource.Size;
|
||||
model.DownloadUrl = resource.DownloadUrl;
|
||||
model.InfoUrl = resource.InfoUrl;
|
||||
model.PosterUrl = resource.PosterUrl;
|
||||
model.CommentUrl = resource.CommentUrl;
|
||||
model.IndexerId = resource.IndexerId;
|
||||
model.Indexer = resource.Indexer;
|
||||
model.DownloadProtocol = resource.Protocol;
|
||||
model.ImdbId = resource.ImdbId;
|
||||
model.PublishDate = resource.PublishDate.ToUniversalTime();
|
||||
|
||||
return model;
|
||||
}
|
||||
public string Query { get; set; }
|
||||
public string Type { get; set; }
|
||||
public List<int> IndexerIds { get; set; }
|
||||
public List<int> Categories { get; set; }
|
||||
public int Limit { get; set; }
|
||||
public int Offset { get; set; }
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user