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, searchingReleases: false,
searchQuery: defaultSearchQuery || '', searchQuery: defaultSearchQuery || '',
searchIndexerIds: defaultIndexerIds, searchIndexerIds: defaultIndexerIds,
searchCategories: defaultCategories searchCategories: defaultCategories,
searchLimit: 100,
searchOffset: 0,
newSearch: true
}; };
} }
@ -115,11 +118,28 @@ class SearchFooter extends Component {
}; };
onSearchPress = () => { 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 }) => { 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, searchQuery,
searchIndexerIds, searchIndexerIds,
searchCategories, searchCategories,
newSearch,
isQueryParameterModalOpen, isQueryParameterModalOpen,
queryModalOptions, queryModalOptions,
searchType searchType
@ -206,7 +227,7 @@ class SearchFooter extends Component {
name='searchIndexerIds' name='searchIndexerIds'
value={searchIndexerIds} value={searchIndexerIds}
isDisabled={isFetching} isDisabled={isFetching}
onChange={onInputChange} onChange={this.onInputChange}
/> />
</div> </div>
@ -220,7 +241,7 @@ class SearchFooter extends Component {
name='searchCategories' name='searchCategories'
value={searchCategories} value={searchCategories}
isDisabled={isFetching} isDisabled={isFetching}
onChange={onInputChange} onChange={this.onInputChange}
/> />
</div> </div>
@ -253,7 +274,7 @@ class SearchFooter extends Component {
isDisabled={isFetching || !hasIndexers} isDisabled={isFetching || !hasIndexers}
onPress={this.onSearchPress} onPress={this.onSearchPress}
> >
{translate('Search')} {newSearch ? translate('Search') : translate('More')}
</SpinnerButton> </SpinnerButton>
</div> </div>
</div> </div>

@ -196,8 +196,8 @@ class SearchIndex extends Component {
this.setState({ jumpToCharacter }); this.setState({ jumpToCharacter });
}; };
onSearchPress = (query, indexerIds, categories, type) => { onSearchPress = (query, indexerIds, categories, type, limit, offset) => {
this.props.onSearchPress({ query, indexerIds, categories, type }); this.props.onSearchPress({ query, indexerIds, categories, type, limit, offset });
}; };
onBulkGrabPress = () => { 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 namespace Prowlarr.Api.V1.Search
{ {
[V1ApiController] [V1ApiController]
public class SearchController : RestController<SearchResource> public class SearchController : RestController<ReleaseResource>
{ {
private readonly ISearchForNzb _nzbSearhService; private readonly ISearchForNzb _nzbSearhService;
private readonly IDownloadService _downloadService; private readonly IDownloadService _downloadService;
@ -46,13 +46,13 @@ namespace Prowlarr.Api.V1.Search
_remoteReleaseCache = cacheManager.GetCache<ReleaseInfo>(GetType(), "remoteReleases"); _remoteReleaseCache = cacheManager.GetCache<ReleaseInfo>(GetType(), "remoteReleases");
} }
public override SearchResource GetResourceById(int id) public override ReleaseResource GetResourceById(int id)
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }
[HttpPost] [HttpPost]
public ActionResult<SearchResource> GrabRelease(SearchResource release) public ActionResult<ReleaseResource> GrabRelease(ReleaseResource release)
{ {
ValidateResource(release); ValidateResource(release);
@ -76,7 +76,7 @@ namespace Prowlarr.Api.V1.Search
} }
[HttpPost("bulk")] [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 source = UserAgentParser.ParseSource(Request.Headers["User-Agent"]);
var host = Request.GetHostName(); var host = Request.GetHostName();
@ -108,35 +108,30 @@ namespace Prowlarr.Api.V1.Search
} }
[HttpGet] [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(payload);
{
return GetSearchReleases(query, type, indexerIds, categories);
}
else
{
return GetSearchReleases(query, type, null, categories);
}
} }
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 try
{ {
var request = new NewznabRequest var request = new NewznabRequest
{ {
q = query, q = payload.Query,
t = type, t = payload.Type,
source = "Prowlarr", source = "Prowlarr",
cat = string.Join(",", categories), cat = string.Join(",", payload.Categories),
server = Request.GetServerUrl(), server = Request.GetServerUrl(),
host = Request.GetHostName() host = Request.GetHostName(),
limit = payload.Limit,
offset = payload.Offset
}; };
request.QueryToParams(); request.QueryToParams();
var result = await _nzbSearhService.Search(request, indexerIds, true); var result = await _nzbSearhService.Search(request, payload.IndexerIds, true);
var decisions = result.Releases; var decisions = result.Releases;
return MapDecisions(decisions, Request.GetServerUrl()); return MapDecisions(decisions, Request.GetServerUrl());
@ -150,12 +145,12 @@ namespace Prowlarr.Api.V1.Search
_logger.Error(ex, "Search failed: " + ex.Message); _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) foreach (var downloadDecision in releases)
{ {
@ -173,7 +168,7 @@ namespace Prowlarr.Api.V1.Search
return result; return result;
} }
private string GetCacheKey(SearchResource resource) private string GetCacheKey(ReleaseResource resource)
{ {
return string.Concat(resource.IndexerId, "_", resource.Guid); return string.Concat(resource.IndexerId, "_", resource.Guid);
} }

@ -1,117 +1,24 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using NzbDrone.Core.Indexers; using System.Text;
using NzbDrone.Core.Parser.Model; using System.Threading.Tasks;
using Prowlarr.Http.REST;
namespace Prowlarr.Api.V1.Search namespace Prowlarr.Api.V1.Search
{ {
public class SearchResource : RestResource public class SearchResource
{ {
public string Guid { get; set; } public SearchResource()
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)
{ {
var releaseInfo = model; Type = "search";
var torrentInfo = (model as TorrentInfo) ?? new TorrentInfo(); Categories = new List<int>();
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
};
} }
public static ReleaseInfo ToModel(this SearchResource resource) public string Query { get; set; }
{ public string Type { get; set; }
ReleaseInfo model; public List<int> IndexerIds { get; set; }
public List<int> Categories { get; set; }
if (resource.Protocol == DownloadProtocol.Torrent) public int Limit { get; set; }
{ public int Offset { get; set; }
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;
}
} }
} }

Loading…
Cancel
Save