New: Load more (page) results on Search UI

pull/907/head
Qstick 3 years ago
parent 83bf3620c0
commit 627da14a32

@ -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 = () => {

@ -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; }
}
}

Loading…
Cancel
Save