Merge remote-tracking branch 'origin/pr/1262'
This commit is contained in:
@@ -21,6 +21,7 @@ package org.airsonic.player.controller;
|
|||||||
|
|
||||||
import org.airsonic.player.domain.*;
|
import org.airsonic.player.domain.*;
|
||||||
import org.airsonic.player.service.*;
|
import org.airsonic.player.service.*;
|
||||||
|
import org.airsonic.player.service.search.IndexManager;
|
||||||
import org.airsonic.player.util.FileUtil;
|
import org.airsonic.player.util.FileUtil;
|
||||||
import org.airsonic.player.util.StringUtil;
|
import org.airsonic.player.util.StringUtil;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
@@ -56,6 +57,8 @@ public class LeftController {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private MediaScannerService mediaScannerService;
|
private MediaScannerService mediaScannerService;
|
||||||
@Autowired
|
@Autowired
|
||||||
|
private IndexManager indexManager;
|
||||||
|
@Autowired
|
||||||
private SettingsService settingsService;
|
private SettingsService settingsService;
|
||||||
@Autowired
|
@Autowired
|
||||||
private SecurityService securityService;
|
private SecurityService securityService;
|
||||||
@@ -116,7 +119,7 @@ public class LeftController {
|
|||||||
boolean musicFolderChanged = saveSelectedMusicFolder(request);
|
boolean musicFolderChanged = saveSelectedMusicFolder(request);
|
||||||
Map<String, Object> map = new HashMap<>();
|
Map<String, Object> map = new HashMap<>();
|
||||||
|
|
||||||
MediaLibraryStatistics statistics = mediaScannerService.getStatistics();
|
MediaLibraryStatistics statistics = indexManager.getStatistics();
|
||||||
Locale locale = RequestContextUtils.getLocale(request);
|
Locale locale = RequestContextUtils.getLocale(request);
|
||||||
|
|
||||||
boolean refresh = ServletRequestUtils.getBooleanParameter(request, "refresh", false);
|
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.AlbumDao;
|
||||||
import org.airsonic.player.dao.ArtistDao;
|
import org.airsonic.player.dao.ArtistDao;
|
||||||
import org.airsonic.player.dao.MediaFileDao;
|
import org.airsonic.player.dao.MediaFileDao;
|
||||||
|
import org.airsonic.player.domain.MediaLibraryStatistics;
|
||||||
import org.airsonic.player.domain.MusicFolder;
|
import org.airsonic.player.domain.MusicFolder;
|
||||||
import org.airsonic.player.service.MediaScannerService;
|
import org.airsonic.player.service.MediaScannerService;
|
||||||
import org.airsonic.player.service.SettingsService;
|
import org.airsonic.player.service.SettingsService;
|
||||||
@@ -103,11 +104,16 @@ public class MusicFolderSettingsController {
|
|||||||
private void expunge() {
|
private void expunge() {
|
||||||
|
|
||||||
// to be before dao#expunge
|
// to be before dao#expunge
|
||||||
LOG.debug("Cleaning search index...");
|
MediaLibraryStatistics statistics = indexManager.getStatistics();
|
||||||
indexManager.startIndexing();
|
if (statistics != null) {
|
||||||
indexManager.expunge();
|
LOG.debug("Cleaning search index...");
|
||||||
indexManager.stopIndexing();
|
indexManager.startIndexing();
|
||||||
LOG.debug("Search index cleanup complete.");
|
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("Cleaning database...");
|
||||||
LOG.debug("Deleting non-present artists...");
|
LOG.debug("Deleting non-present artists...");
|
||||||
|
|||||||
+51
-41
@@ -19,9 +19,10 @@
|
|||||||
*/
|
*/
|
||||||
package org.airsonic.player.domain;
|
package org.airsonic.player.domain;
|
||||||
|
|
||||||
import org.airsonic.player.util.StringUtil;
|
import javax.validation.constraints.NotNull;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
import java.util.Date;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Contains media libaray statistics, including the number of artists, albums and songs.
|
* Contains media libaray statistics, including the number of artists, albums and songs.
|
||||||
@@ -31,31 +32,37 @@ import org.slf4j.LoggerFactory;
|
|||||||
*/
|
*/
|
||||||
public class MediaLibraryStatistics {
|
public class MediaLibraryStatistics {
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(MediaLibraryStatistics.class);
|
@NotNull
|
||||||
|
private Integer artistCount;
|
||||||
private int artistCount;
|
@NotNull
|
||||||
private int albumCount;
|
private Integer albumCount;
|
||||||
private int songCount;
|
@NotNull
|
||||||
private long totalLengthInBytes;
|
private Integer songCount;
|
||||||
private long totalDurationInSeconds;
|
@NotNull
|
||||||
|
private Long totalLengthInBytes;
|
||||||
public MediaLibraryStatistics(int artistCount, int albumCount, int songCount, long totalLengthInBytes, long totalDurationInSeconds) {
|
@NotNull
|
||||||
this.artistCount = artistCount;
|
private Long totalDurationInSeconds;
|
||||||
this.albumCount = albumCount;
|
@NotNull
|
||||||
this.songCount = songCount;
|
private Date scanDate;
|
||||||
this.totalLengthInBytes = totalLengthInBytes;
|
|
||||||
this.totalDurationInSeconds = totalDurationInSeconds;
|
|
||||||
}
|
|
||||||
|
|
||||||
public MediaLibraryStatistics() {
|
public MediaLibraryStatistics() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void reset() {
|
public MediaLibraryStatistics(Date scanDate) {
|
||||||
|
if (scanDate == null) {
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
}
|
||||||
|
this.scanDate = scanDate;
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void reset() {
|
||||||
artistCount = 0;
|
artistCount = 0;
|
||||||
albumCount = 0;
|
albumCount = 0;
|
||||||
songCount = 0;
|
songCount = 0;
|
||||||
totalLengthInBytes = 0;
|
totalLengthInBytes = 0L;
|
||||||
totalDurationInSeconds = 0;
|
totalDurationInSeconds = 0L;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void incrementArtists(int n) {
|
public void incrementArtists(int n) {
|
||||||
@@ -78,42 +85,45 @@ public class MediaLibraryStatistics {
|
|||||||
totalDurationInSeconds += n;
|
totalDurationInSeconds += n;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getArtistCount() {
|
public Integer getArtistCount() {
|
||||||
return artistCount;
|
return artistCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getAlbumCount() {
|
public Integer getAlbumCount() {
|
||||||
return albumCount;
|
return albumCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getSongCount() {
|
public Integer getSongCount() {
|
||||||
return songCount;
|
return songCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getTotalLengthInBytes() {
|
public Long getTotalLengthInBytes() {
|
||||||
return totalLengthInBytes;
|
return totalLengthInBytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getTotalDurationInSeconds() {
|
public Long getTotalDurationInSeconds() {
|
||||||
return totalDurationInSeconds;
|
return totalDurationInSeconds;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String format() {
|
public Date getScanDate() {
|
||||||
return artistCount + " " + albumCount + " " + songCount + " " + totalLengthInBytes + " " + totalDurationInSeconds;
|
return scanDate;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static MediaLibraryStatistics parse(String s) {
|
@Override
|
||||||
try {
|
public boolean equals(Object o) {
|
||||||
String[] strings = StringUtil.split(s);
|
if (this == o) return true;
|
||||||
return new MediaLibraryStatistics(
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
Integer.parseInt(strings[0]),
|
MediaLibraryStatistics that = (MediaLibraryStatistics) o;
|
||||||
Integer.parseInt(strings[1]),
|
return Objects.equals(artistCount, that.artistCount) &&
|
||||||
Integer.parseInt(strings[2]),
|
Objects.equals(albumCount, that.albumCount) &&
|
||||||
Long.parseLong(strings[3]),
|
Objects.equals(songCount, that.songCount) &&
|
||||||
Long.parseLong(strings[4]));
|
Objects.equals(totalLengthInBytes, that.totalLengthInBytes) &&
|
||||||
} catch (Exception e) {
|
Objects.equals(totalDurationInSeconds, that.totalDurationInSeconds) &&
|
||||||
LOG.warn("Failed to parse media library statistics: " + s);
|
Objects.equals(scanDate, that.scanDate);
|
||||||
return new MediaLibraryStatistics();
|
}
|
||||||
}
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(artistCount, albumCount, songCount, totalLengthInBytes, totalDurationInSeconds, scanDate);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,8 +51,6 @@ public class MediaScannerService {
|
|||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(MediaScannerService.class);
|
private static final Logger LOG = LoggerFactory.getLogger(MediaScannerService.class);
|
||||||
|
|
||||||
private MediaLibraryStatistics statistics;
|
|
||||||
|
|
||||||
private boolean scanning;
|
private boolean scanning;
|
||||||
|
|
||||||
private ScheduledExecutorService scheduler;
|
private ScheduledExecutorService scheduler;
|
||||||
@@ -76,13 +74,11 @@ public class MediaScannerService {
|
|||||||
@PostConstruct
|
@PostConstruct
|
||||||
public void init() {
|
public void init() {
|
||||||
indexManager.initializeIndexDirectory();
|
indexManager.initializeIndexDirectory();
|
||||||
statistics = settingsService.getMediaLibraryStatistics();
|
|
||||||
schedule();
|
schedule();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void initNoSchedule() {
|
public void initNoSchedule() {
|
||||||
indexManager.deleteOldIndexFiles();
|
indexManager.deleteOldIndexFiles();
|
||||||
statistics = settingsService.getMediaLibraryStatistics();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -115,12 +111,16 @@ public class MediaScannerService {
|
|||||||
LOG.info("Automatic media library scanning scheduled to run every {} day(s), starting at {}", daysBetween, nextRun);
|
LOG.info("Automatic media library scanning scheduled to run every {} day(s), starting at {}", daysBetween, nextRun);
|
||||||
|
|
||||||
// In addition, create index immediately if it doesn't exist on disk.
|
// 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.");
|
LOG.info("Media library never scanned. Doing it now.");
|
||||||
scanLibrary();
|
scanLibrary();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boolean neverScanned() {
|
||||||
|
return indexManager.getStatistics() == null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether the media library is currently being scanned.
|
* Returns whether the media library is currently being scanned.
|
||||||
*/
|
*/
|
||||||
@@ -160,8 +160,9 @@ public class MediaScannerService {
|
|||||||
|
|
||||||
private void doScanLibrary() {
|
private void doScanLibrary() {
|
||||||
LOG.info("Starting to scan media library.");
|
LOG.info("Starting to scan media library.");
|
||||||
Date lastScanned = DateUtils.truncate(new Date(), Calendar.SECOND);
|
MediaLibraryStatistics statistics = new MediaLibraryStatistics(
|
||||||
LOG.debug("New last scan date is " + lastScanned);
|
DateUtils.truncate(new Date(), Calendar.SECOND));
|
||||||
|
LOG.debug("New last scan date is " + statistics.getScanDate());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
@@ -170,7 +171,6 @@ public class MediaScannerService {
|
|||||||
Genres genres = new Genres();
|
Genres genres = new Genres();
|
||||||
|
|
||||||
scanCount = 0;
|
scanCount = 0;
|
||||||
statistics.reset();
|
|
||||||
|
|
||||||
mediaFileService.setMemoryCacheEnabled(false);
|
mediaFileService.setMemoryCacheEnabled(false);
|
||||||
indexManager.startIndexing();
|
indexManager.startIndexing();
|
||||||
@@ -180,24 +180,24 @@ public class MediaScannerService {
|
|||||||
// Recurse through all files on disk.
|
// Recurse through all files on disk.
|
||||||
for (MusicFolder musicFolder : settingsService.getAllMusicFolders()) {
|
for (MusicFolder musicFolder : settingsService.getAllMusicFolders()) {
|
||||||
MediaFile root = mediaFileService.getMediaFile(musicFolder.getPath(), false);
|
MediaFile root = mediaFileService.getMediaFile(musicFolder.getPath(), false);
|
||||||
scanFile(root, musicFolder, lastScanned, albumCount, genres, false);
|
scanFile(root, musicFolder, statistics, albumCount, genres, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Scan podcast folder.
|
// Scan podcast folder.
|
||||||
File podcastFolder = new File(settingsService.getPodcastFolder());
|
File podcastFolder = new File(settingsService.getPodcastFolder());
|
||||||
if (podcastFolder.exists()) {
|
if (podcastFolder.exists()) {
|
||||||
scanFile(mediaFileService.getMediaFile(podcastFolder), new MusicFolder(podcastFolder, null, true, null),
|
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("Scanned media library with " + scanCount + " entries.");
|
||||||
|
|
||||||
LOG.info("Marking non-present files.");
|
LOG.info("Marking non-present files.");
|
||||||
mediaFileDao.markNonPresent(lastScanned);
|
mediaFileDao.markNonPresent(statistics.getScanDate());
|
||||||
LOG.info("Marking non-present artists.");
|
LOG.info("Marking non-present artists.");
|
||||||
artistDao.markNonPresent(lastScanned);
|
artistDao.markNonPresent(statistics.getScanDate());
|
||||||
LOG.info("Marking non-present albums.");
|
LOG.info("Marking non-present albums.");
|
||||||
albumDao.markNonPresent(lastScanned);
|
albumDao.markNonPresent(statistics.getScanDate());
|
||||||
|
|
||||||
// Update statistics
|
// Update statistics
|
||||||
statistics.incrementArtists(albumCount.size());
|
statistics.incrementArtists(albumCount.size());
|
||||||
@@ -208,21 +208,18 @@ public class MediaScannerService {
|
|||||||
// Update genres
|
// Update genres
|
||||||
mediaFileDao.updateGenres(genres.getGenres());
|
mediaFileDao.updateGenres(genres.getGenres());
|
||||||
|
|
||||||
settingsService.setMediaLibraryStatistics(statistics);
|
|
||||||
settingsService.setLastScanned(lastScanned);
|
|
||||||
settingsService.save(false);
|
|
||||||
LOG.info("Completed media library scan.");
|
LOG.info("Completed media library scan.");
|
||||||
|
|
||||||
} catch (Throwable x) {
|
} catch (Throwable x) {
|
||||||
LOG.error("Failed to scan media library.", x);
|
LOG.error("Failed to scan media library.", x);
|
||||||
} finally {
|
} finally {
|
||||||
mediaFileService.setMemoryCacheEnabled(true);
|
mediaFileService.setMemoryCacheEnabled(true);
|
||||||
indexManager.stopIndexing();
|
indexManager.stopIndexing(statistics);
|
||||||
scanning = false;
|
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) {
|
Map<String, Integer> albumCount, Genres genres, boolean isPodcast) {
|
||||||
scanCount++;
|
scanCount++;
|
||||||
if (scanCount % 250 == 0) {
|
if (scanCount % 250 == 0) {
|
||||||
@@ -241,22 +238,22 @@ public class MediaScannerService {
|
|||||||
|
|
||||||
if (file.isDirectory()) {
|
if (file.isDirectory()) {
|
||||||
for (MediaFile child : mediaFileService.getChildrenOf(file, true, false, false, false)) {
|
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)) {
|
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 {
|
} else {
|
||||||
if (!isPodcast) {
|
if (!isPodcast) {
|
||||||
updateAlbum(file, musicFolder, lastScanned, albumCount);
|
updateAlbum(file, musicFolder, statistics.getScanDate(), albumCount);
|
||||||
updateArtist(file, musicFolder, lastScanned, albumCount);
|
updateArtist(file, musicFolder, statistics.getScanDate(), albumCount);
|
||||||
}
|
}
|
||||||
statistics.incrementSongs(1);
|
statistics.incrementSongs(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateGenres(file, genres);
|
updateGenres(file, genres);
|
||||||
mediaFileDao.markPresent(file.getPath(), lastScanned);
|
mediaFileDao.markPresent(file.getPath(), statistics.getScanDate());
|
||||||
artistDao.markPresent(file.getAlbumArtist(), lastScanned);
|
artistDao.markPresent(file.getAlbumArtist(), statistics.getScanDate());
|
||||||
|
|
||||||
if (file.getDurationSeconds() != null) {
|
if (file.getDurationSeconds() != null) {
|
||||||
statistics.incrementTotalDurationInSeconds(file.getDurationSeconds());
|
statistics.incrementTotalDurationInSeconds(file.getDurationSeconds());
|
||||||
@@ -368,15 +365,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) {
|
public void setSettingsService(SettingsService settingsService) {
|
||||||
this.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_LDAP_AUTO_SHADOWING = "LdapAutoShadowing";
|
||||||
private static final String KEY_GETTING_STARTED_ENABLED = "GettingStartedEnabled";
|
private static final String KEY_GETTING_STARTED_ENABLED = "GettingStartedEnabled";
|
||||||
private static final String KEY_SETTINGS_CHANGED = "SettingsChanged";
|
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_ORGANIZE_BY_FOLDER_STRUCTURE = "OrganizeByFolderStructure";
|
||||||
private static final String KEY_SORT_ALBUMS_BY_YEAR = "SortAlbumsByYear";
|
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_ENABLED = "DlnaEnabled";
|
||||||
private static final String KEY_DLNA_SERVER_NAME = "DlnaServerName";
|
private static final String KEY_DLNA_SERVER_NAME = "DlnaServerName";
|
||||||
private static final String KEY_DLNA_BASE_LAN_URL = "DlnaBaseLANURL";
|
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 long DEFAULT_SETTINGS_CHANGED = 0L;
|
||||||
private static final boolean DEFAULT_ORGANIZE_BY_FOLDER_STRUCTURE = true;
|
private static final boolean DEFAULT_ORGANIZE_BY_FOLDER_STRUCTURE = true;
|
||||||
private static final boolean DEFAULT_SORT_ALBUMS_BY_YEAR = 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 boolean DEFAULT_DLNA_ENABLED = false;
|
||||||
private static final String DEFAULT_DLNA_SERVER_NAME = "Airsonic";
|
private static final String DEFAULT_DLNA_SERVER_NAME = "Airsonic";
|
||||||
private static final String DEFAULT_DLNA_BASE_LAN_URL = null;
|
private static final String DEFAULT_DLNA_BASE_LAN_URL = null;
|
||||||
@@ -220,6 +217,7 @@ public class SettingsService {
|
|||||||
"CoverArtFileTypes", "UrlRedirectCustomHost", "CoverArtLimit", "StreamPort",
|
"CoverArtFileTypes", "UrlRedirectCustomHost", "CoverArtLimit", "StreamPort",
|
||||||
"PortForwardingEnabled", "RewriteUrl", "UrlRedirectCustomUrl", "UrlRedirectContextPath",
|
"PortForwardingEnabled", "RewriteUrl", "UrlRedirectCustomUrl", "UrlRedirectContextPath",
|
||||||
"UrlRedirectFrom", "UrlRedirectionEnabled", "UrlRedirectType", "Port", "HttpsPort",
|
"UrlRedirectFrom", "UrlRedirectionEnabled", "UrlRedirectType", "Port", "HttpsPort",
|
||||||
|
"MediaLibraryStatistics", "LastScanned",
|
||||||
// Database settings renamed
|
// Database settings renamed
|
||||||
"database.varchar.maxlength", "database.config.type", "database.config.embed.driver",
|
"database.varchar.maxlength", "database.config.type", "database.config.embed.driver",
|
||||||
"database.config.embed.url", "database.config.embed.username", "database.config.embed.password",
|
"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);
|
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() {
|
public boolean isOrganizeByFolderStructure() {
|
||||||
return getBoolean(KEY_ORGANIZE_BY_FOLDER_STRUCTURE, DEFAULT_ORGANIZE_BY_FOLDER_STRUCTURE);
|
return getBoolean(KEY_ORGANIZE_BY_FOLDER_STRUCTURE, DEFAULT_ORGANIZE_BY_FOLDER_STRUCTURE);
|
||||||
}
|
}
|
||||||
@@ -787,14 +772,6 @@ public class SettingsService {
|
|||||||
return excludePattern;
|
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.
|
* 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.AlbumDao;
|
||||||
import org.airsonic.player.dao.ArtistDao;
|
import org.airsonic.player.dao.ArtistDao;
|
||||||
import org.airsonic.player.dao.MediaFileDao;
|
import org.airsonic.player.dao.MediaFileDao;
|
||||||
import org.airsonic.player.domain.Album;
|
import org.airsonic.player.domain.*;
|
||||||
import org.airsonic.player.domain.Artist;
|
|
||||||
import org.airsonic.player.domain.MediaFile;
|
|
||||||
import org.airsonic.player.domain.MusicFolder;
|
|
||||||
import org.airsonic.player.service.SettingsService;
|
import org.airsonic.player.service.SettingsService;
|
||||||
import org.airsonic.player.util.FileUtil;
|
import org.airsonic.player.util.FileUtil;
|
||||||
|
import org.airsonic.player.util.Util;
|
||||||
import org.apache.commons.io.FileUtils;
|
import org.apache.commons.io.FileUtils;
|
||||||
import org.apache.lucene.document.Document;
|
import org.apache.lucene.document.Document;
|
||||||
import org.apache.lucene.index.IndexWriter;
|
import org.apache.lucene.index.*;
|
||||||
import org.apache.lucene.index.IndexWriterConfig;
|
|
||||||
import org.apache.lucene.index.Term;
|
|
||||||
import org.apache.lucene.search.IndexSearcher;
|
import org.apache.lucene.search.IndexSearcher;
|
||||||
import org.apache.lucene.search.SearcherManager;
|
import org.apache.lucene.search.SearcherManager;
|
||||||
import org.apache.lucene.store.FSDirectory;
|
import org.apache.lucene.store.FSDirectory;
|
||||||
@@ -47,6 +43,8 @@ import java.io.File;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.EnumMap;
|
import java.util.EnumMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
import java.util.regex.Pattern;
|
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 INDEX_ROOT_DIR_NAME = "index";
|
||||||
|
|
||||||
|
private static final String MEDIA_STATISTICS_KEY = "stats";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* File supplier for index directory.
|
* File supplier for index directory.
|
||||||
*/
|
*/
|
||||||
@@ -221,26 +221,29 @@ public class IndexManager {
|
|||||||
* Close Writer of all indexes and update SearcherManager.
|
* Close Writer of all indexes and update SearcherManager.
|
||||||
* Called at the end of the Scan flow.
|
* Called at the end of the Scan flow.
|
||||||
*/
|
*/
|
||||||
public void stopIndexing() {
|
public void stopIndexing(MediaLibraryStatistics statistics) {
|
||||||
Arrays.asList(IndexType.values()).forEach(this::stopIndexing);
|
Arrays.asList(IndexType.values()).forEach(indexType -> stopIndexing(indexType, statistics));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Close Writer of specified index and refresh SearcherManager.
|
* Close Writer of specified index and refresh SearcherManager.
|
||||||
*/
|
*/
|
||||||
private void stopIndexing(IndexType type) {
|
private void stopIndexing(IndexType type, MediaLibraryStatistics statistics) {
|
||||||
|
|
||||||
boolean isUpdate = false;
|
boolean isUpdate = false;
|
||||||
// close
|
// close
|
||||||
|
IndexWriter indexWriter = writers.get(type);
|
||||||
try {
|
try {
|
||||||
isUpdate = -1 != writers.get(type).commit();
|
Map<String,String> userData = Util.objectToStringMap(statistics);
|
||||||
writers.get(type).close();
|
indexWriter.setLiveCommitData(userData.entrySet());
|
||||||
|
isUpdate = -1 != indexWriter.commit();
|
||||||
|
indexWriter.close();
|
||||||
writers.remove(type);
|
writers.remove(type);
|
||||||
LOG.trace("Success to create or update search index : [" + type + "]");
|
LOG.trace("Success to create or update search index : [" + type + "]");
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
LOG.error("Failed to create search index.", e);
|
LOG.error("Failed to create search index.", e);
|
||||||
} finally {
|
} finally {
|
||||||
FileUtil.closeQuietly(writers.get(type));
|
FileUtil.closeQuietly(indexWriter);
|
||||||
}
|
}
|
||||||
|
|
||||||
// refresh reader as index may have been written
|
// 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.
|
* Return the IndexSearcher of the specified index.
|
||||||
* At initial startup, it may return null
|
* 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.domain.*;
|
||||||
import org.airsonic.player.service.MediaFileService;
|
import org.airsonic.player.service.MediaFileService;
|
||||||
import org.airsonic.player.service.PlaylistService;
|
import org.airsonic.player.service.PlaylistService;
|
||||||
|
import org.airsonic.player.service.search.IndexManager;
|
||||||
import org.airsonic.player.util.Util;
|
import org.airsonic.player.util.Util;
|
||||||
import org.fourthline.cling.support.contentdirectory.ContentDirectoryErrorCode;
|
import org.fourthline.cling.support.contentdirectory.ContentDirectoryErrorCode;
|
||||||
import org.fourthline.cling.support.contentdirectory.ContentDirectoryException;
|
import org.fourthline.cling.support.contentdirectory.ContentDirectoryException;
|
||||||
@@ -57,6 +58,8 @@ public class FolderBasedContentDirectory extends CustomContentDirectory {
|
|||||||
private MediaFileService mediaFileService;
|
private MediaFileService mediaFileService;
|
||||||
@Autowired
|
@Autowired
|
||||||
private PlaylistService playlistService;
|
private PlaylistService playlistService;
|
||||||
|
@Autowired
|
||||||
|
private IndexManager indexManager;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public BrowseResult browse(String objectId, BrowseFlag browseFlag, String filter, long firstResult,
|
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.setId(CONTAINER_ID_ROOT);
|
||||||
root.setParentID("-1");
|
root.setParentID("-1");
|
||||||
|
|
||||||
MediaLibraryStatistics statistics = settingsService.getMediaLibraryStatistics();
|
MediaLibraryStatistics statistics = indexManager.getStatistics();
|
||||||
root.setStorageUsed(statistics == null ? 0 : statistics.getTotalLengthInBytes());
|
root.setStorageUsed(statistics == null ? 0 : statistics.getTotalLengthInBytes());
|
||||||
root.setTitle("Airsonic Media");
|
root.setTitle("Airsonic Media");
|
||||||
root.setRestricted(true);
|
root.setRestricted(true);
|
||||||
|
|||||||
@@ -20,10 +20,12 @@
|
|||||||
package org.airsonic.player.service.upnp;
|
package org.airsonic.player.service.upnp;
|
||||||
|
|
||||||
import org.airsonic.player.domain.MediaLibraryStatistics;
|
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.DIDLContent;
|
||||||
import org.fourthline.cling.support.model.WriteStatus;
|
import org.fourthline.cling.support.model.WriteStatus;
|
||||||
import org.fourthline.cling.support.model.container.Container;
|
import org.fourthline.cling.support.model.container.Container;
|
||||||
import org.fourthline.cling.support.model.container.StorageFolder;
|
import org.fourthline.cling.support.model.container.StorageFolder;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -35,12 +37,16 @@ import java.util.List;
|
|||||||
*/
|
*/
|
||||||
@Component
|
@Component
|
||||||
public class RootUpnpProcessor extends UpnpContentProcessor <Container, Container> {
|
public class RootUpnpProcessor extends UpnpContentProcessor <Container, Container> {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
IndexManager indexManager;
|
||||||
|
|
||||||
public Container createRootContainer() {
|
public Container createRootContainer() {
|
||||||
StorageFolder root = new StorageFolder();
|
StorageFolder root = new StorageFolder();
|
||||||
root.setId(DispatchingContentDirectory.CONTAINER_ID_ROOT);
|
root.setId(DispatchingContentDirectory.CONTAINER_ID_ROOT);
|
||||||
root.setParentID("-1");
|
root.setParentID("-1");
|
||||||
|
|
||||||
MediaLibraryStatistics statistics = getDispatchingContentDirectory().getSettingsService().getMediaLibraryStatistics();
|
MediaLibraryStatistics statistics = indexManager.getStatistics();
|
||||||
// returning large storageUsed values doesn't play nicely with
|
// returning large storageUsed values doesn't play nicely with
|
||||||
// some upnp clients
|
// some upnp clients
|
||||||
//root.setStorageUsed(statistics == null ? 0 : statistics.getTotalLengthInBytes());
|
//root.setStorageUsed(statistics == null ? 0 : statistics.getTotalLengthInBytes());
|
||||||
|
|||||||
@@ -20,6 +20,8 @@
|
|||||||
package org.airsonic.player.util;
|
package org.airsonic.player.util;
|
||||||
|
|
||||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
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 com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import org.apache.commons.lang3.SystemUtils;
|
import org.apache.commons.lang3.SystemUtils;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
@@ -30,10 +32,12 @@ import org.springframework.web.util.UriComponentsBuilder;
|
|||||||
import javax.servlet.ServletResponse;
|
import javax.servlet.ServletResponse;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
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.*;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Miscellaneous general utility methods.
|
* Miscellaneous general utility methods.
|
||||||
@@ -113,7 +117,11 @@ public final class Util {
|
|||||||
return result;
|
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) {
|
public static String debugObject(Object object) {
|
||||||
try {
|
try {
|
||||||
return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(object);
|
return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(object);
|
||||||
@@ -169,4 +177,29 @@ public final class Util {
|
|||||||
return false;
|
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);
|
assertNotNull(mediaFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNeverScanned() {
|
||||||
|
|
||||||
|
mediaScannerService.neverScanned();
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testMusicBrainzReleaseIdTag() {
|
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. */
|
/* Does not scan, only expunges the index. */
|
||||||
indexManager.startIndexing();
|
indexManager.startIndexing();
|
||||||
indexManager.expunge();
|
indexManager.expunge();
|
||||||
indexManager.stopIndexing();
|
indexManager.stopIndexing(indexManager.getStatistics());
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Subsequent search results.
|
* 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