Move index meta information
Signed-off-by: Andrew DeMaria <lostonamountain@gmail.com>
This commit is contained in:
@@ -21,6 +21,7 @@ package org.airsonic.player.controller;
|
||||
|
||||
import org.airsonic.player.domain.*;
|
||||
import org.airsonic.player.service.*;
|
||||
import org.airsonic.player.service.search.IndexManager;
|
||||
import org.airsonic.player.util.FileUtil;
|
||||
import org.airsonic.player.util.StringUtil;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -56,6 +57,8 @@ public class LeftController {
|
||||
@Autowired
|
||||
private MediaScannerService mediaScannerService;
|
||||
@Autowired
|
||||
private IndexManager indexManager;
|
||||
@Autowired
|
||||
private SettingsService settingsService;
|
||||
@Autowired
|
||||
private SecurityService securityService;
|
||||
@@ -116,7 +119,7 @@ public class LeftController {
|
||||
boolean musicFolderChanged = saveSelectedMusicFolder(request);
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
|
||||
MediaLibraryStatistics statistics = mediaScannerService.getStatistics();
|
||||
MediaLibraryStatistics statistics = indexManager.getStatistics();
|
||||
Locale locale = RequestContextUtils.getLocale(request);
|
||||
|
||||
boolean refresh = ServletRequestUtils.getBooleanParameter(request, "refresh", false);
|
||||
|
||||
+11
-5
@@ -23,6 +23,7 @@ import org.airsonic.player.command.MusicFolderSettingsCommand;
|
||||
import org.airsonic.player.dao.AlbumDao;
|
||||
import org.airsonic.player.dao.ArtistDao;
|
||||
import org.airsonic.player.dao.MediaFileDao;
|
||||
import org.airsonic.player.domain.MediaLibraryStatistics;
|
||||
import org.airsonic.player.domain.MusicFolder;
|
||||
import org.airsonic.player.service.MediaScannerService;
|
||||
import org.airsonic.player.service.SettingsService;
|
||||
@@ -103,11 +104,16 @@ public class MusicFolderSettingsController {
|
||||
private void expunge() {
|
||||
|
||||
// to be before dao#expunge
|
||||
LOG.debug("Cleaning search index...");
|
||||
indexManager.startIndexing();
|
||||
indexManager.expunge();
|
||||
indexManager.stopIndexing();
|
||||
LOG.debug("Search index cleanup complete.");
|
||||
MediaLibraryStatistics statistics = indexManager.getStatistics();
|
||||
if (statistics != null) {
|
||||
LOG.debug("Cleaning search index...");
|
||||
indexManager.startIndexing();
|
||||
indexManager.expunge();
|
||||
indexManager.stopIndexing(statistics);
|
||||
LOG.debug("Search index cleanup complete.");
|
||||
} else {
|
||||
LOG.warn("Missing index statistics - index probably hasn't been created yet. Not expunging index.");
|
||||
}
|
||||
|
||||
LOG.debug("Cleaning database...");
|
||||
LOG.debug("Deleting non-present artists...");
|
||||
|
||||
+51
-41
@@ -19,9 +19,10 @@
|
||||
*/
|
||||
package org.airsonic.player.domain;
|
||||
|
||||
import org.airsonic.player.util.StringUtil;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Contains media libaray statistics, including the number of artists, albums and songs.
|
||||
@@ -31,31 +32,37 @@ import org.slf4j.LoggerFactory;
|
||||
*/
|
||||
public class MediaLibraryStatistics {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(MediaLibraryStatistics.class);
|
||||
|
||||
private int artistCount;
|
||||
private int albumCount;
|
||||
private int songCount;
|
||||
private long totalLengthInBytes;
|
||||
private long totalDurationInSeconds;
|
||||
|
||||
public MediaLibraryStatistics(int artistCount, int albumCount, int songCount, long totalLengthInBytes, long totalDurationInSeconds) {
|
||||
this.artistCount = artistCount;
|
||||
this.albumCount = albumCount;
|
||||
this.songCount = songCount;
|
||||
this.totalLengthInBytes = totalLengthInBytes;
|
||||
this.totalDurationInSeconds = totalDurationInSeconds;
|
||||
}
|
||||
@NotNull
|
||||
private Integer artistCount;
|
||||
@NotNull
|
||||
private Integer albumCount;
|
||||
@NotNull
|
||||
private Integer songCount;
|
||||
@NotNull
|
||||
private Long totalLengthInBytes;
|
||||
@NotNull
|
||||
private Long totalDurationInSeconds;
|
||||
@NotNull
|
||||
private Date scanDate;
|
||||
|
||||
public MediaLibraryStatistics() {
|
||||
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
public MediaLibraryStatistics(Date scanDate) {
|
||||
if (scanDate == null) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
this.scanDate = scanDate;
|
||||
reset();
|
||||
}
|
||||
|
||||
protected void reset() {
|
||||
artistCount = 0;
|
||||
albumCount = 0;
|
||||
songCount = 0;
|
||||
totalLengthInBytes = 0;
|
||||
totalDurationInSeconds = 0;
|
||||
totalLengthInBytes = 0L;
|
||||
totalDurationInSeconds = 0L;
|
||||
}
|
||||
|
||||
public void incrementArtists(int n) {
|
||||
@@ -78,42 +85,45 @@ public class MediaLibraryStatistics {
|
||||
totalDurationInSeconds += n;
|
||||
}
|
||||
|
||||
public int getArtistCount() {
|
||||
public Integer getArtistCount() {
|
||||
return artistCount;
|
||||
}
|
||||
|
||||
public int getAlbumCount() {
|
||||
public Integer getAlbumCount() {
|
||||
return albumCount;
|
||||
}
|
||||
|
||||
public int getSongCount() {
|
||||
public Integer getSongCount() {
|
||||
return songCount;
|
||||
}
|
||||
|
||||
public long getTotalLengthInBytes() {
|
||||
public Long getTotalLengthInBytes() {
|
||||
return totalLengthInBytes;
|
||||
}
|
||||
|
||||
public long getTotalDurationInSeconds() {
|
||||
public Long getTotalDurationInSeconds() {
|
||||
return totalDurationInSeconds;
|
||||
}
|
||||
|
||||
public String format() {
|
||||
return artistCount + " " + albumCount + " " + songCount + " " + totalLengthInBytes + " " + totalDurationInSeconds;
|
||||
public Date getScanDate() {
|
||||
return scanDate;
|
||||
}
|
||||
|
||||
public static MediaLibraryStatistics parse(String s) {
|
||||
try {
|
||||
String[] strings = StringUtil.split(s);
|
||||
return new MediaLibraryStatistics(
|
||||
Integer.parseInt(strings[0]),
|
||||
Integer.parseInt(strings[1]),
|
||||
Integer.parseInt(strings[2]),
|
||||
Long.parseLong(strings[3]),
|
||||
Long.parseLong(strings[4]));
|
||||
} catch (Exception e) {
|
||||
LOG.warn("Failed to parse media library statistics: " + s);
|
||||
return new MediaLibraryStatistics();
|
||||
}
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
MediaLibraryStatistics that = (MediaLibraryStatistics) o;
|
||||
return Objects.equals(artistCount, that.artistCount) &&
|
||||
Objects.equals(albumCount, that.albumCount) &&
|
||||
Objects.equals(songCount, that.songCount) &&
|
||||
Objects.equals(totalLengthInBytes, that.totalLengthInBytes) &&
|
||||
Objects.equals(totalDurationInSeconds, that.totalDurationInSeconds) &&
|
||||
Objects.equals(scanDate, that.scanDate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(artistCount, albumCount, songCount, totalLengthInBytes, totalDurationInSeconds, scanDate);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,8 +46,6 @@ public class MediaScannerService {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(MediaScannerService.class);
|
||||
|
||||
private MediaLibraryStatistics statistics;
|
||||
|
||||
private boolean scanning;
|
||||
private Timer timer;
|
||||
@Autowired
|
||||
@@ -69,13 +67,11 @@ public class MediaScannerService {
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
indexManager.initializeIndexDirectory();
|
||||
statistics = settingsService.getMediaLibraryStatistics();
|
||||
schedule();
|
||||
}
|
||||
|
||||
public void initNoSchedule() {
|
||||
indexManager.deleteOldIndexFiles();
|
||||
statistics = settingsService.getMediaLibraryStatistics();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -120,12 +116,16 @@ public class MediaScannerService {
|
||||
LOG.info("Automatic media library scanning scheduled to run every " + daysBetween + " day(s), starting at " + firstTime);
|
||||
|
||||
// In addition, create index immediately if it doesn't exist on disk.
|
||||
if (settingsService.getLastScanned() == null) {
|
||||
if (neverScanned()) {
|
||||
LOG.info("Media library never scanned. Doing it now.");
|
||||
scanLibrary();
|
||||
}
|
||||
}
|
||||
|
||||
boolean neverScanned() {
|
||||
return indexManager.getStatistics() == null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the media library is currently being scanned.
|
||||
*/
|
||||
@@ -165,8 +165,9 @@ public class MediaScannerService {
|
||||
|
||||
private void doScanLibrary() {
|
||||
LOG.info("Starting to scan media library.");
|
||||
Date lastScanned = DateUtils.truncate(new Date(), Calendar.SECOND);
|
||||
LOG.debug("New last scan date is " + lastScanned);
|
||||
MediaLibraryStatistics statistics = new MediaLibraryStatistics(
|
||||
DateUtils.truncate(new Date(), Calendar.SECOND));
|
||||
LOG.debug("New last scan date is " + statistics.getScanDate());
|
||||
|
||||
try {
|
||||
|
||||
@@ -175,7 +176,6 @@ public class MediaScannerService {
|
||||
Genres genres = new Genres();
|
||||
|
||||
scanCount = 0;
|
||||
statistics.reset();
|
||||
|
||||
mediaFileService.setMemoryCacheEnabled(false);
|
||||
indexManager.startIndexing();
|
||||
@@ -185,24 +185,24 @@ public class MediaScannerService {
|
||||
// Recurse through all files on disk.
|
||||
for (MusicFolder musicFolder : settingsService.getAllMusicFolders()) {
|
||||
MediaFile root = mediaFileService.getMediaFile(musicFolder.getPath(), false);
|
||||
scanFile(root, musicFolder, lastScanned, albumCount, genres, false);
|
||||
scanFile(root, musicFolder, statistics, albumCount, genres, false);
|
||||
}
|
||||
|
||||
// Scan podcast folder.
|
||||
File podcastFolder = new File(settingsService.getPodcastFolder());
|
||||
if (podcastFolder.exists()) {
|
||||
scanFile(mediaFileService.getMediaFile(podcastFolder), new MusicFolder(podcastFolder, null, true, null),
|
||||
lastScanned, albumCount, genres, true);
|
||||
statistics, albumCount, genres, true);
|
||||
}
|
||||
|
||||
LOG.info("Scanned media library with " + scanCount + " entries.");
|
||||
|
||||
LOG.info("Marking non-present files.");
|
||||
mediaFileDao.markNonPresent(lastScanned);
|
||||
mediaFileDao.markNonPresent(statistics.getScanDate());
|
||||
LOG.info("Marking non-present artists.");
|
||||
artistDao.markNonPresent(lastScanned);
|
||||
artistDao.markNonPresent(statistics.getScanDate());
|
||||
LOG.info("Marking non-present albums.");
|
||||
albumDao.markNonPresent(lastScanned);
|
||||
albumDao.markNonPresent(statistics.getScanDate());
|
||||
|
||||
// Update statistics
|
||||
statistics.incrementArtists(albumCount.size());
|
||||
@@ -213,21 +213,18 @@ public class MediaScannerService {
|
||||
// Update genres
|
||||
mediaFileDao.updateGenres(genres.getGenres());
|
||||
|
||||
settingsService.setMediaLibraryStatistics(statistics);
|
||||
settingsService.setLastScanned(lastScanned);
|
||||
settingsService.save(false);
|
||||
LOG.info("Completed media library scan.");
|
||||
|
||||
} catch (Throwable x) {
|
||||
LOG.error("Failed to scan media library.", x);
|
||||
} finally {
|
||||
mediaFileService.setMemoryCacheEnabled(true);
|
||||
indexManager.stopIndexing();
|
||||
indexManager.stopIndexing(statistics);
|
||||
scanning = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void scanFile(MediaFile file, MusicFolder musicFolder, Date lastScanned,
|
||||
private void scanFile(MediaFile file, MusicFolder musicFolder, MediaLibraryStatistics statistics,
|
||||
Map<String, Integer> albumCount, Genres genres, boolean isPodcast) {
|
||||
scanCount++;
|
||||
if (scanCount % 250 == 0) {
|
||||
@@ -246,22 +243,22 @@ public class MediaScannerService {
|
||||
|
||||
if (file.isDirectory()) {
|
||||
for (MediaFile child : mediaFileService.getChildrenOf(file, true, false, false, false)) {
|
||||
scanFile(child, musicFolder, lastScanned, albumCount, genres, isPodcast);
|
||||
scanFile(child, musicFolder, statistics, albumCount, genres, isPodcast);
|
||||
}
|
||||
for (MediaFile child : mediaFileService.getChildrenOf(file, false, true, false, false)) {
|
||||
scanFile(child, musicFolder, lastScanned, albumCount, genres, isPodcast);
|
||||
scanFile(child, musicFolder, statistics, albumCount, genres, isPodcast);
|
||||
}
|
||||
} else {
|
||||
if (!isPodcast) {
|
||||
updateAlbum(file, musicFolder, lastScanned, albumCount);
|
||||
updateArtist(file, musicFolder, lastScanned, albumCount);
|
||||
updateAlbum(file, musicFolder, statistics.getScanDate(), albumCount);
|
||||
updateArtist(file, musicFolder, statistics.getScanDate(), albumCount);
|
||||
}
|
||||
statistics.incrementSongs(1);
|
||||
}
|
||||
|
||||
updateGenres(file, genres);
|
||||
mediaFileDao.markPresent(file.getPath(), lastScanned);
|
||||
artistDao.markPresent(file.getAlbumArtist(), lastScanned);
|
||||
mediaFileDao.markPresent(file.getPath(), statistics.getScanDate());
|
||||
artistDao.markPresent(file.getAlbumArtist(), statistics.getScanDate());
|
||||
|
||||
if (file.getDurationSeconds() != null) {
|
||||
statistics.incrementTotalDurationInSeconds(file.getDurationSeconds());
|
||||
@@ -373,15 +370,6 @@ public class MediaScannerService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns media library statistics, including the number of artists, albums and songs.
|
||||
*
|
||||
* @return Media library statistics.
|
||||
*/
|
||||
public MediaLibraryStatistics getStatistics() {
|
||||
return statistics;
|
||||
}
|
||||
|
||||
public void setSettingsService(SettingsService settingsService) {
|
||||
this.settingsService = settingsService;
|
||||
}
|
||||
|
||||
@@ -96,10 +96,8 @@ public class SettingsService {
|
||||
private static final String KEY_LDAP_AUTO_SHADOWING = "LdapAutoShadowing";
|
||||
private static final String KEY_GETTING_STARTED_ENABLED = "GettingStartedEnabled";
|
||||
private static final String KEY_SETTINGS_CHANGED = "SettingsChanged";
|
||||
private static final String KEY_LAST_SCANNED = "LastScanned";
|
||||
private static final String KEY_ORGANIZE_BY_FOLDER_STRUCTURE = "OrganizeByFolderStructure";
|
||||
private static final String KEY_SORT_ALBUMS_BY_YEAR = "SortAlbumsByYear";
|
||||
private static final String KEY_MEDIA_LIBRARY_STATISTICS = "MediaLibraryStatistics";
|
||||
private static final String KEY_DLNA_ENABLED = "DlnaEnabled";
|
||||
private static final String KEY_DLNA_SERVER_NAME = "DlnaServerName";
|
||||
private static final String KEY_DLNA_BASE_LAN_URL = "DlnaBaseLANURL";
|
||||
@@ -182,7 +180,6 @@ public class SettingsService {
|
||||
private static final long DEFAULT_SETTINGS_CHANGED = 0L;
|
||||
private static final boolean DEFAULT_ORGANIZE_BY_FOLDER_STRUCTURE = true;
|
||||
private static final boolean DEFAULT_SORT_ALBUMS_BY_YEAR = true;
|
||||
private static final String DEFAULT_MEDIA_LIBRARY_STATISTICS = "0 0 0 0 0";
|
||||
private static final boolean DEFAULT_DLNA_ENABLED = false;
|
||||
private static final String DEFAULT_DLNA_SERVER_NAME = "Airsonic";
|
||||
private static final String DEFAULT_DLNA_BASE_LAN_URL = null;
|
||||
@@ -220,6 +217,7 @@ public class SettingsService {
|
||||
"CoverArtFileTypes", "UrlRedirectCustomHost", "CoverArtLimit", "StreamPort",
|
||||
"PortForwardingEnabled", "RewriteUrl", "UrlRedirectCustomUrl", "UrlRedirectContextPath",
|
||||
"UrlRedirectFrom", "UrlRedirectionEnabled", "UrlRedirectType", "Port", "HttpsPort",
|
||||
"MediaLibraryStatistics", "LastScanned",
|
||||
// Database settings renamed
|
||||
"database.varchar.maxlength", "database.config.type", "database.config.embed.driver",
|
||||
"database.config.embed.url", "database.config.embed.username", "database.config.embed.password",
|
||||
@@ -726,19 +724,6 @@ public class SettingsService {
|
||||
return getLong(KEY_SETTINGS_CHANGED, DEFAULT_SETTINGS_CHANGED);
|
||||
}
|
||||
|
||||
public Date getLastScanned() {
|
||||
String lastScanned = getProperty(KEY_LAST_SCANNED, null);
|
||||
return lastScanned == null ? null : new Date(Long.parseLong(lastScanned));
|
||||
}
|
||||
|
||||
void setLastScanned(Date date) {
|
||||
if (date == null) {
|
||||
setProperty(KEY_LAST_SCANNED, null);
|
||||
} else {
|
||||
setLong(KEY_LAST_SCANNED, date.getTime());
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isOrganizeByFolderStructure() {
|
||||
return getBoolean(KEY_ORGANIZE_BY_FOLDER_STRUCTURE, DEFAULT_ORGANIZE_BY_FOLDER_STRUCTURE);
|
||||
}
|
||||
@@ -787,14 +772,6 @@ public class SettingsService {
|
||||
return excludePattern;
|
||||
}
|
||||
|
||||
public MediaLibraryStatistics getMediaLibraryStatistics() {
|
||||
return MediaLibraryStatistics.parse(getString(KEY_MEDIA_LIBRARY_STATISTICS, DEFAULT_MEDIA_LIBRARY_STATISTICS));
|
||||
}
|
||||
|
||||
void setMediaLibraryStatistics(MediaLibraryStatistics statistics) {
|
||||
setString(KEY_MEDIA_LIBRARY_STATISTICS, statistics.format());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether we are running in Development mode.
|
||||
*
|
||||
|
||||
@@ -23,17 +23,13 @@ package org.airsonic.player.service.search;
|
||||
import org.airsonic.player.dao.AlbumDao;
|
||||
import org.airsonic.player.dao.ArtistDao;
|
||||
import org.airsonic.player.dao.MediaFileDao;
|
||||
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.airsonic.player.domain.*;
|
||||
import org.airsonic.player.service.SettingsService;
|
||||
import org.airsonic.player.util.FileUtil;
|
||||
import org.airsonic.player.util.Util;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.apache.lucene.document.Document;
|
||||
import org.apache.lucene.index.IndexWriter;
|
||||
import org.apache.lucene.index.IndexWriterConfig;
|
||||
import org.apache.lucene.index.Term;
|
||||
import org.apache.lucene.index.*;
|
||||
import org.apache.lucene.search.IndexSearcher;
|
||||
import org.apache.lucene.search.SearcherManager;
|
||||
import org.apache.lucene.store.FSDirectory;
|
||||
@@ -47,6 +43,8 @@ import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.EnumMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.regex.Pattern;
|
||||
@@ -83,6 +81,8 @@ public class IndexManager {
|
||||
*/
|
||||
private static final String INDEX_ROOT_DIR_NAME = "index";
|
||||
|
||||
private static final String MEDIA_STATISTICS_KEY = "stats";
|
||||
|
||||
/**
|
||||
* File supplier for index directory.
|
||||
*/
|
||||
@@ -221,26 +221,29 @@ public class IndexManager {
|
||||
* Close Writer of all indexes and update SearcherManager.
|
||||
* Called at the end of the Scan flow.
|
||||
*/
|
||||
public void stopIndexing() {
|
||||
Arrays.asList(IndexType.values()).forEach(this::stopIndexing);
|
||||
public void stopIndexing(MediaLibraryStatistics statistics) {
|
||||
Arrays.asList(IndexType.values()).forEach(indexType -> stopIndexing(indexType, statistics));
|
||||
}
|
||||
|
||||
/**
|
||||
* Close Writer of specified index and refresh SearcherManager.
|
||||
*/
|
||||
private void stopIndexing(IndexType type) {
|
||||
private void stopIndexing(IndexType type, MediaLibraryStatistics statistics) {
|
||||
|
||||
boolean isUpdate = false;
|
||||
// close
|
||||
IndexWriter indexWriter = writers.get(type);
|
||||
try {
|
||||
isUpdate = -1 != writers.get(type).commit();
|
||||
writers.get(type).close();
|
||||
Map<String,String> userData = Util.objectToStringMap(statistics);
|
||||
indexWriter.setLiveCommitData(userData.entrySet());
|
||||
isUpdate = -1 != indexWriter.commit();
|
||||
indexWriter.close();
|
||||
writers.remove(type);
|
||||
LOG.trace("Success to create or update search index : [" + type + "]");
|
||||
} catch (IOException e) {
|
||||
LOG.error("Failed to create search index.", e);
|
||||
} finally {
|
||||
FileUtil.closeQuietly(writers.get(type));
|
||||
FileUtil.closeQuietly(indexWriter);
|
||||
}
|
||||
|
||||
// refresh reader as index may have been written
|
||||
@@ -256,6 +259,43 @@ public class IndexManager {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the MediaLibraryStatistics saved on commit in the index. Ensures that each index reports the same data.
|
||||
* On invalid indices, returns null.
|
||||
*/
|
||||
public @Nullable MediaLibraryStatistics getStatistics() {
|
||||
MediaLibraryStatistics stats = null;
|
||||
for (IndexType indexType : IndexType.values()) {
|
||||
IndexSearcher searcher = getSearcher(indexType);
|
||||
if (searcher == null) {
|
||||
LOG.trace("No index for type " + indexType);
|
||||
return null;
|
||||
}
|
||||
IndexReader indexReader = searcher.getIndexReader();
|
||||
if (!(indexReader instanceof DirectoryReader)) {
|
||||
LOG.warn("Unexpected index type " + indexReader.getClass());
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
Map<String, String> userData = ((DirectoryReader) indexReader).getIndexCommit().getUserData();
|
||||
MediaLibraryStatistics currentStats = Util.stringMapToValidObject(MediaLibraryStatistics.class,
|
||||
userData);
|
||||
if (stats == null) {
|
||||
stats = currentStats;
|
||||
} else {
|
||||
if (!Objects.equals(stats, currentStats)) {
|
||||
LOG.warn("Index type " + indexType + " had differing stats data");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
} catch (IOException | IllegalArgumentException e) {
|
||||
LOG.warn("Exception encountered while fetching index commit data", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return stats;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the IndexSearcher of the specified index.
|
||||
* At initial startup, it may return null
|
||||
|
||||
+4
-1
@@ -22,6 +22,7 @@ package org.airsonic.player.service.upnp;
|
||||
import org.airsonic.player.domain.*;
|
||||
import org.airsonic.player.service.MediaFileService;
|
||||
import org.airsonic.player.service.PlaylistService;
|
||||
import org.airsonic.player.service.search.IndexManager;
|
||||
import org.airsonic.player.util.Util;
|
||||
import org.fourthline.cling.support.contentdirectory.ContentDirectoryErrorCode;
|
||||
import org.fourthline.cling.support.contentdirectory.ContentDirectoryException;
|
||||
@@ -57,6 +58,8 @@ public class FolderBasedContentDirectory extends CustomContentDirectory {
|
||||
private MediaFileService mediaFileService;
|
||||
@Autowired
|
||||
private PlaylistService playlistService;
|
||||
@Autowired
|
||||
private IndexManager indexManager;
|
||||
|
||||
@Override
|
||||
public BrowseResult browse(String objectId, BrowseFlag browseFlag, String filter, long firstResult,
|
||||
@@ -98,7 +101,7 @@ public class FolderBasedContentDirectory extends CustomContentDirectory {
|
||||
root.setId(CONTAINER_ID_ROOT);
|
||||
root.setParentID("-1");
|
||||
|
||||
MediaLibraryStatistics statistics = settingsService.getMediaLibraryStatistics();
|
||||
MediaLibraryStatistics statistics = indexManager.getStatistics();
|
||||
root.setStorageUsed(statistics == null ? 0 : statistics.getTotalLengthInBytes());
|
||||
root.setTitle("Airsonic Media");
|
||||
root.setRestricted(true);
|
||||
|
||||
@@ -20,10 +20,12 @@
|
||||
package org.airsonic.player.service.upnp;
|
||||
|
||||
import org.airsonic.player.domain.MediaLibraryStatistics;
|
||||
import org.airsonic.player.service.search.IndexManager;
|
||||
import org.fourthline.cling.support.model.DIDLContent;
|
||||
import org.fourthline.cling.support.model.WriteStatus;
|
||||
import org.fourthline.cling.support.model.container.Container;
|
||||
import org.fourthline.cling.support.model.container.StorageFolder;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -35,12 +37,16 @@ import java.util.List;
|
||||
*/
|
||||
@Component
|
||||
public class RootUpnpProcessor extends UpnpContentProcessor <Container, Container> {
|
||||
|
||||
@Autowired
|
||||
IndexManager indexManager;
|
||||
|
||||
public Container createRootContainer() {
|
||||
StorageFolder root = new StorageFolder();
|
||||
root.setId(DispatchingContentDirectory.CONTAINER_ID_ROOT);
|
||||
root.setParentID("-1");
|
||||
|
||||
MediaLibraryStatistics statistics = getDispatchingContentDirectory().getSettingsService().getMediaLibraryStatistics();
|
||||
MediaLibraryStatistics statistics = indexManager.getStatistics();
|
||||
// returning large storageUsed values doesn't play nicely with
|
||||
// some upnp clients
|
||||
//root.setStorageUsed(statistics == null ? 0 : statistics.getTotalLengthInBytes());
|
||||
|
||||
@@ -20,6 +20,8 @@
|
||||
package org.airsonic.player.util;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
import org.slf4j.Logger;
|
||||
@@ -30,10 +32,12 @@ import org.springframework.web.util.UriComponentsBuilder;
|
||||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.validation.ConstraintViolation;
|
||||
import javax.validation.Validation;
|
||||
import javax.validation.Validator;
|
||||
import javax.validation.ValidatorFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Miscellaneous general utility methods.
|
||||
@@ -113,7 +117,11 @@ public final class Util {
|
||||
return result;
|
||||
}
|
||||
|
||||
static ObjectMapper objectMapper = new ObjectMapper();
|
||||
private static ObjectMapper objectMapper = new ObjectMapper();
|
||||
static {
|
||||
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||
}
|
||||
|
||||
public static String debugObject(Object object) {
|
||||
try {
|
||||
return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(object);
|
||||
@@ -169,4 +177,29 @@ public final class Util {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static Map<String, String> objectToStringMap(Object object) {
|
||||
TypeReference<HashMap<String, String>> typeReference = new TypeReference<HashMap<String, String>>() {};
|
||||
return objectMapper.convertValue(object, typeReference);
|
||||
}
|
||||
|
||||
public static <T> T stringMapToObject(Class<T> clazz, Map<String, String> data) {
|
||||
return objectMapper.convertValue(data, clazz);
|
||||
}
|
||||
|
||||
private static Validator validator;
|
||||
static {
|
||||
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
|
||||
validator = factory.getValidator();
|
||||
}
|
||||
|
||||
public static <T> T stringMapToValidObject(Class<T> clazz, Map<String, String> data) {
|
||||
T object = stringMapToObject(clazz, data);
|
||||
Set<ConstraintViolation<T>> validate = validator.validate(object);
|
||||
if (validate.isEmpty()) {
|
||||
return object;
|
||||
} else {
|
||||
throw new IllegalArgumentException("Created object was not valid");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+6
@@ -178,6 +178,12 @@ public class MediaScannerServiceTestCase {
|
||||
assertNotNull(mediaFile);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNeverScanned() {
|
||||
|
||||
mediaScannerService.neverScanned();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMusicBrainzReleaseIdTag() {
|
||||
|
||||
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
package org.airsonic.player.service;
|
||||
|
||||
import org.airsonic.player.domain.MediaLibraryStatistics;
|
||||
import org.airsonic.player.service.search.IndexManager;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class MediaScannerServiceUnitTest {
|
||||
|
||||
@InjectMocks
|
||||
MediaScannerService mediaScannerService;
|
||||
|
||||
@Mock
|
||||
IndexManager indexManager;
|
||||
|
||||
@Test
|
||||
public void neverScanned() {
|
||||
when(indexManager.getStatistics()).thenReturn(null);
|
||||
assertTrue(mediaScannerService.neverScanned());
|
||||
|
||||
when(indexManager.getStatistics()).thenReturn(new MediaLibraryStatistics());
|
||||
assertFalse(mediaScannerService.neverScanned());
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -172,7 +172,7 @@ public class IndexManagerTestCase extends AbstractAirsonicHomeTest {
|
||||
/* Does not scan, only expunges the index. */
|
||||
indexManager.startIndexing();
|
||||
indexManager.expunge();
|
||||
indexManager.stopIndexing();
|
||||
indexManager.stopIndexing(indexManager.getStatistics());
|
||||
|
||||
/*
|
||||
* Subsequent search results.
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
package org.airsonic.player.util;
|
||||
|
||||
import org.airsonic.player.domain.MediaLibraryStatistics;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class UtilTest {
|
||||
|
||||
@Test
|
||||
public void objectToStringMapNull() {
|
||||
MediaLibraryStatistics statistics = null;
|
||||
Map<String, String> stringStringMap = Util.objectToStringMap(statistics);
|
||||
assertNull(stringStringMap);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void objectToStringMap() {
|
||||
Date date = new Date(1568350960725L);
|
||||
MediaLibraryStatistics statistics = new MediaLibraryStatistics(date);
|
||||
statistics.incrementAlbums(5);
|
||||
statistics.incrementSongs(4);
|
||||
statistics.incrementArtists(910823);
|
||||
statistics.incrementTotalDurationInSeconds(30);
|
||||
statistics.incrementTotalLengthInBytes(2930491082L);
|
||||
Map<String, String> stringStringMap = Util.objectToStringMap(statistics);
|
||||
assertEquals("5", stringStringMap.get("albumCount"));
|
||||
assertEquals("4", stringStringMap.get("songCount"));
|
||||
assertEquals("910823", stringStringMap.get("artistCount"));
|
||||
assertEquals("30", stringStringMap.get("totalDurationInSeconds"));
|
||||
assertEquals("2930491082", stringStringMap.get("totalLengthInBytes"));
|
||||
assertEquals("1568350960725", stringStringMap.get("scanDate"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void stringMapToObject() {
|
||||
Map<String, String> stringStringMap = new HashMap<>();
|
||||
stringStringMap.put("albumCount", "5");
|
||||
stringStringMap.put("songCount", "4");
|
||||
stringStringMap.put("artistCount", "910823");
|
||||
stringStringMap.put("totalDurationInSeconds", "30");
|
||||
stringStringMap.put("totalLengthInBytes", "2930491082");
|
||||
stringStringMap.put("scanDate", "1568350960725");
|
||||
MediaLibraryStatistics statistics = Util.stringMapToObject(MediaLibraryStatistics.class, stringStringMap);
|
||||
assertEquals(new Integer(5), statistics.getAlbumCount());
|
||||
assertEquals(new Integer(4), statistics.getSongCount());
|
||||
assertEquals(new Integer(910823), statistics.getArtistCount());
|
||||
assertEquals(new Long(30L), statistics.getTotalDurationInSeconds());
|
||||
assertEquals(new Long(2930491082L), statistics.getTotalLengthInBytes());
|
||||
assertEquals(new Date(1568350960725L), statistics.getScanDate());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void stringMapToObjectWithExtraneousData() {
|
||||
Map<String, String> stringStringMap = new HashMap<>();
|
||||
stringStringMap.put("albumCount", "5");
|
||||
stringStringMap.put("songCount", "4");
|
||||
stringStringMap.put("artistCount", "910823");
|
||||
stringStringMap.put("totalDurationInSeconds", "30");
|
||||
stringStringMap.put("totalLengthInBytes", "2930491082");
|
||||
stringStringMap.put("scanDate", "1568350960725");
|
||||
stringStringMap.put("extraneousData", "nothingHereToLookAt");
|
||||
MediaLibraryStatistics statistics = Util.stringMapToObject(MediaLibraryStatistics.class, stringStringMap);
|
||||
assertEquals(new Integer(5), statistics.getAlbumCount());
|
||||
assertEquals(new Integer(4), statistics.getSongCount());
|
||||
assertEquals(new Integer(910823), statistics.getArtistCount());
|
||||
assertEquals(new Long(30L), statistics.getTotalDurationInSeconds());
|
||||
assertEquals(new Long(2930491082L), statistics.getTotalLengthInBytes());
|
||||
assertEquals(new Date(1568350960725L), statistics.getScanDate());
|
||||
}
|
||||
|
||||
public void stringMapToObjectWithNoData() {
|
||||
Map<String, String> stringStringMap = new HashMap<>();
|
||||
MediaLibraryStatistics statistics = Util.stringMapToObject(MediaLibraryStatistics.class, stringStringMap);
|
||||
assertNotNull(statistics);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void stringMapToValidObjectWithNoData() {
|
||||
Map<String, String> stringStringMap = new HashMap<>();
|
||||
MediaLibraryStatistics statistics = Util.stringMapToValidObject(MediaLibraryStatistics.class, stringStringMap);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user