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.
237 lines
7.8 KiB
237 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.Album;
|
|
import org.airsonic.player.domain.Artist;
|
|
import org.airsonic.player.domain.MediaFile;
|
|
import org.airsonic.player.domain.MusicFolder;
|
|
import org.apache.lucene.document.Document;
|
|
import org.apache.lucene.document.Field.Store;
|
|
import org.apache.lucene.document.FieldType;
|
|
import org.apache.lucene.document.IntPoint;
|
|
import org.apache.lucene.document.SortedDocValuesField;
|
|
import org.apache.lucene.document.StoredField;
|
|
import org.apache.lucene.document.TextField;
|
|
import org.apache.lucene.index.IndexOptions;
|
|
import org.apache.lucene.index.Term;
|
|
import org.apache.lucene.util.BytesRef;
|
|
import org.checkerframework.checker.nullness.qual.NonNull;
|
|
import org.checkerframework.checker.nullness.qual.Nullable;
|
|
import org.springframework.stereotype.Component;
|
|
|
|
import java.util.function.BiConsumer;
|
|
|
|
import static org.springframework.util.ObjectUtils.isEmpty;
|
|
|
|
/**
|
|
* A factory that generates the documents to be stored in the index.
|
|
*/
|
|
@Component
|
|
public class DocumentFactory {
|
|
|
|
private static final FieldType TYPE_ID;
|
|
|
|
private static final FieldType TYPE_ID_NO_STORE;
|
|
|
|
private static final FieldType TYPE_KEY;
|
|
|
|
static {
|
|
|
|
TYPE_ID = new FieldType();
|
|
TYPE_ID.setIndexOptions(IndexOptions.DOCS);
|
|
TYPE_ID.setTokenized(false);
|
|
TYPE_ID.setOmitNorms(true);
|
|
TYPE_ID.setStored(true);
|
|
TYPE_ID.freeze();
|
|
|
|
TYPE_ID_NO_STORE = new FieldType();
|
|
TYPE_ID_NO_STORE.setIndexOptions(IndexOptions.DOCS);
|
|
TYPE_ID_NO_STORE.setTokenized(false);
|
|
TYPE_ID_NO_STORE.setOmitNorms(true);
|
|
TYPE_ID_NO_STORE.setStored(false);
|
|
TYPE_ID_NO_STORE.freeze();
|
|
|
|
TYPE_KEY = new FieldType();
|
|
TYPE_KEY.setIndexOptions(IndexOptions.DOCS);
|
|
TYPE_KEY.setTokenized(false);
|
|
TYPE_KEY.setOmitNorms(true);
|
|
TYPE_KEY.setStored(false);
|
|
TYPE_KEY.freeze();
|
|
|
|
}
|
|
|
|
@FunctionalInterface
|
|
private interface Consumer<T, U, V> {
|
|
void accept(T t, U u, V v);
|
|
|
|
}
|
|
|
|
private BiConsumer<@NonNull Document, @NonNull Integer> fieldId = (doc, value) -> {
|
|
doc.add(new StoredField(FieldNames.ID, Integer.toString(value), TYPE_ID));
|
|
};
|
|
|
|
private BiConsumer<@NonNull Document, @NonNull Integer> fieldFolderId = (doc, value) -> {
|
|
doc.add(new StoredField(FieldNames.FOLDER_ID, Integer.toString(value), TYPE_ID_NO_STORE));
|
|
};
|
|
|
|
private Consumer<@NonNull Document, @NonNull String, @NonNull String> fieldKey = (doc, field, value) -> {
|
|
doc.add(new StoredField(field, value, TYPE_KEY));
|
|
};
|
|
|
|
private BiConsumer<@NonNull Document, @NonNull String> fieldMediatype = (doc, value) ->
|
|
fieldKey.accept(doc, FieldNames.MEDIA_TYPE, value);
|
|
|
|
private BiConsumer<@NonNull Document, @NonNull String> fieldFolderPath = (doc, value) ->
|
|
fieldKey.accept(doc, FieldNames.FOLDER, value);
|
|
|
|
private BiConsumer<@NonNull Document, @Nullable String> fieldGenre = (doc, value) -> {
|
|
if (isEmpty(value)) {
|
|
return;
|
|
}
|
|
fieldKey.accept(doc, FieldNames.GENRE, value);
|
|
};
|
|
|
|
private Consumer<@NonNull Document, @NonNull String, @Nullable Integer> fieldYear = (doc, fieldName, value) -> {
|
|
if (isEmpty(value)) {
|
|
return;
|
|
}
|
|
doc.add(new IntPoint(fieldName, value));
|
|
};
|
|
|
|
private Consumer<@NonNull Document, @NonNull String, @Nullable String> fieldWords = (doc, fieldName, value) -> {
|
|
if (isEmpty(value)) {
|
|
return;
|
|
}
|
|
doc.add(new TextField(fieldName, value, Store.NO));
|
|
doc.add(new SortedDocValuesField(fieldName, new BytesRef(value)));
|
|
};
|
|
|
|
public final Term createPrimarykey(Integer id) {
|
|
return new Term(FieldNames.ID, Integer.toString(id));
|
|
}
|
|
|
|
public final Term createPrimarykey(Album album) {
|
|
return createPrimarykey(album.getId());
|
|
}
|
|
|
|
public final Term createPrimarykey(Artist artist) {
|
|
return createPrimarykey(artist.getId());
|
|
}
|
|
|
|
public final Term createPrimarykey(MediaFile mediaFile) {
|
|
return createPrimarykey(mediaFile.getId());
|
|
}
|
|
|
|
/**
|
|
* Create a document.
|
|
*
|
|
* @param mediaFile target of document
|
|
* @return document
|
|
* @since legacy
|
|
*/
|
|
public Document createAlbumDocument(MediaFile mediaFile) {
|
|
Document doc = new Document();
|
|
fieldId.accept(doc, mediaFile.getId());
|
|
fieldWords.accept(doc, FieldNames.ARTIST, mediaFile.getArtist());
|
|
fieldWords.accept(doc, FieldNames.ALBUM, mediaFile.getAlbumName());
|
|
fieldFolderPath.accept(doc, mediaFile.getFolder());
|
|
return doc;
|
|
}
|
|
|
|
/**
|
|
* Create a document.
|
|
*
|
|
* @param mediaFile target of document
|
|
* @return document
|
|
* @since legacy
|
|
*/
|
|
public Document createArtistDocument(MediaFile mediaFile) {
|
|
Document doc = new Document();
|
|
fieldId.accept(doc, mediaFile.getId());
|
|
fieldWords.accept(doc, FieldNames.ARTIST, mediaFile.getArtist());
|
|
fieldFolderPath.accept(doc, mediaFile.getFolder());
|
|
return doc;
|
|
}
|
|
|
|
/**
|
|
* Create a document.
|
|
*
|
|
* @param album target of document
|
|
* @return document
|
|
* @since legacy
|
|
*/
|
|
public Document createAlbumId3Document(Album album) {
|
|
Document doc = new Document();
|
|
fieldId.accept(doc, album.getId());
|
|
fieldWords.accept(doc, FieldNames.ARTIST, album.getArtist());
|
|
fieldWords.accept(doc, FieldNames.ALBUM, album.getName());
|
|
fieldFolderId.accept(doc, album.getFolderId());
|
|
return doc;
|
|
}
|
|
|
|
/**
|
|
* Create a document.
|
|
*
|
|
* @param artist target of document
|
|
* @param musicFolder target folder exists
|
|
* @return document
|
|
* @since legacy
|
|
*/
|
|
/*
|
|
* XXX 3.x -> 8.x :
|
|
* Only null check specification of createArtistId3Document is different from legacy.
|
|
* (The reason is only to simplify the function.)
|
|
*
|
|
* Since the field of domain object Album is nonnull,
|
|
* null check was not performed.
|
|
*
|
|
* In implementation ARTIST and ALBUM became nullable,
|
|
* but null is not input at this point in data flow.
|
|
*/
|
|
public Document createArtistId3Document(Artist artist, MusicFolder musicFolder) {
|
|
Document doc = new Document();
|
|
fieldId.accept(doc, artist.getId());
|
|
fieldWords.accept(doc, FieldNames.ARTIST, artist.getName());
|
|
fieldFolderId.accept(doc, musicFolder.getId());
|
|
return doc;
|
|
}
|
|
|
|
/**
|
|
* Create a document.
|
|
*
|
|
* @param mediaFile target of document
|
|
* @return document
|
|
* @since legacy
|
|
*/
|
|
public Document createSongDocument(MediaFile mediaFile) {
|
|
Document doc = new Document();
|
|
fieldId.accept(doc, mediaFile.getId());
|
|
fieldMediatype.accept(doc, mediaFile.getMediaType().name());
|
|
fieldWords.accept(doc, FieldNames.TITLE, mediaFile.getTitle());
|
|
fieldWords.accept(doc, FieldNames.ARTIST, mediaFile.getArtist());
|
|
fieldGenre.accept(doc, mediaFile.getGenre());
|
|
fieldYear.accept(doc, FieldNames.YEAR, mediaFile.getYear());
|
|
fieldFolderPath.accept(doc, mediaFile.getFolder());
|
|
return doc;
|
|
}
|
|
|
|
}
|
|
|