My fork of airsonic with experimental fixes and improvements. See branch "custom"
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

238 lines
7.8 KiB

/*
This file is part of Airsonic.
Airsonic is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Airsonic is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Airsonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2016 (C) Airsonic Authors
Based upon Subsonic, Copyright 2009 (C) Sindre Mehus
*/
package org.airsonic.player.service.search;
import org.airsonic.player.domain.*;
import org.airsonic.player.service.SearchService;
import org.apache.lucene.document.Document;
import org.apache.lucene.search.*;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.util.*;
import java.util.function.BiConsumer;
import java.util.stream.Collectors;
import static org.airsonic.player.service.search.IndexType.*;
import static org.springframework.util.ObjectUtils.isEmpty;
@Service
public class SearchServiceImpl implements SearchService {
private static final Logger LOG = LoggerFactory.getLogger(SearchServiceImpl.class);
@Autowired
private QueryFactory queryFactory;
@Autowired
private IndexManager indexManager;
@Autowired
private SearchServiceUtilities util;
@Override
public SearchResult search(SearchCriteria criteria, List<MusicFolder> musicFolders,
IndexType indexType) {
SearchResult result = new SearchResult();
int offset = criteria.getOffset();
int count = criteria.getCount();
result.setOffset(offset);
if (count <= 0)
return result;
IndexSearcher searcher = indexManager.getSearcher(indexType);
if (isEmpty(searcher)) {
return result;
}
try {
Query query = queryFactory.search(criteria, musicFolders, indexType);
TopDocs topDocs = searcher.search(query, offset + count);
int totalHits = util.round.apply(topDocs.totalHits.value);
result.setTotalHits(totalHits);
int start = Math.min(offset, totalHits);
int end = Math.min(start + count, totalHits);
for (int i = start; i < end; i++) {
util.addIfAnyMatch(result, indexType, searcher.doc(topDocs.scoreDocs[i].doc));
}
} catch (IOException e) {
LOG.error("Failed to execute Lucene search.", e);
} finally {
indexManager.release(indexType, searcher);
}
return result;
}
/**
* Common processing of random method.
*
* @param count Number of albums to return.
* @param id2ListCallBack Callback to get D from id and store it in List
*/
private final <D> List<D> createRandomDocsList(
int count, IndexSearcher searcher, Query query, BiConsumer<List<D>, Integer> id2ListCallBack)
throws IOException {
List<Integer> docs = Arrays
.stream(searcher.search(query, Integer.MAX_VALUE).scoreDocs)
.map(sd -> sd.doc)
.collect(Collectors.toList());
List<D> result = new ArrayList<>();
while (!docs.isEmpty() && result.size() < count) {
int randomPos = util.nextInt.apply(docs.size());
Document document = searcher.doc(docs.get(randomPos));
id2ListCallBack.accept(result, util.getId.apply(document));
docs.remove(randomPos);
}
return result;
}
@Override
public List<MediaFile> getRandomSongs(RandomSearchCriteria criteria) {
IndexSearcher searcher = indexManager.getSearcher(SONG);
if (isEmpty(searcher)) {
// At first start
return Collections.emptyList();
}
try {
Query query = queryFactory.getRandomSongs(criteria);
return createRandomDocsList(criteria.getCount(), searcher, query,
(dist, id) -> util.addIgnoreNull(dist, SONG, id));
} catch (IOException e) {
LOG.error("Failed to search or random songs.", e);
} finally {
indexManager.release(IndexType.SONG, searcher);
}
return Collections.emptyList();
}
@Override
public List<MediaFile> getRandomAlbums(int count, List<MusicFolder> musicFolders) {
IndexSearcher searcher = indexManager.getSearcher(IndexType.ALBUM);
if (isEmpty(searcher)) {
return Collections.emptyList();
}
Query query = queryFactory.getRandomAlbums(musicFolders);
try {
return createRandomDocsList(count, searcher, query,
(dist, id) -> util.addIgnoreNull(dist, ALBUM, id));
} catch (IOException e) {
LOG.error("Failed to search for random albums.", e);
} finally {
indexManager.release(IndexType.ALBUM, searcher);
}
return Collections.emptyList();
}
@Override
public List<Album> getRandomAlbumsId3(int count, List<MusicFolder> musicFolders) {
IndexSearcher searcher = indexManager.getSearcher(IndexType.ALBUM_ID3);
if (isEmpty(searcher)) {
return Collections.emptyList();
}
Query query = queryFactory.getRandomAlbumsId3(musicFolders);
try {
return createRandomDocsList(count, searcher, query,
(dist, id) -> util.addIgnoreNull(dist, ALBUM_ID3, id));
} catch (IOException e) {
LOG.error("Failed to search for random albums.", e);
} finally {
indexManager.release(IndexType.ALBUM_ID3, searcher);
}
return Collections.emptyList();
}
@Override
public <T> ParamSearchResult<T> searchByName(String name, int offset, int count,
List<MusicFolder> folderList, Class<T> assignableClass) {
// we only support album, artist, and song for now
@Nullable
IndexType indexType = util.getIndexType.apply(assignableClass);
@Nullable
String fieldName = util.getFieldName.apply(assignableClass);
ParamSearchResult<T> result = new ParamSearchResult<T>();
result.setOffset(offset);
if (isEmpty(indexType) || isEmpty(fieldName) || count <= 0) {
return result;
}
IndexSearcher searcher = indexManager.getSearcher(indexType);
if (isEmpty(searcher)) {
return result;
}
try {
Query query = queryFactory.searchByName(fieldName, name);
SortField[] sortFields = Arrays
.stream(indexType.getFields())
.map(n -> new SortField(n, SortField.Type.STRING))
.toArray(i -> new SortField[i]);
Sort sort = new Sort(sortFields);
TopDocs topDocs = searcher.search(query, offset + count, sort);
int totalHits = util.round.apply(topDocs.totalHits.value);
result.setTotalHits(totalHits);
int start = Math.min(offset, totalHits);
int end = Math.min(start + count, totalHits);
for (int i = start; i < end; i++) {
Document doc = searcher.doc(topDocs.scoreDocs[i].doc);
util.addIgnoreNull(result, indexType, util.getId.apply(doc), assignableClass);
}
} catch (IOException e) {
LOG.error("Failed to execute Lucene search.", e);
} finally {
indexManager.release(indexType, searcher);
}
return result;
}
}