Merge remote-tracking branch 'origin/pr/1262'

master
Andrew DeMaria 5 years ago
commit eda6406865
No known key found for this signature in database
GPG Key ID: 0A3F5E91F8364EDF
  1. 5
      airsonic-main/src/main/java/org/airsonic/player/controller/LeftController.java
  2. 16
      airsonic-main/src/main/java/org/airsonic/player/controller/MusicFolderSettingsController.java
  3. 88
      airsonic-main/src/main/java/org/airsonic/player/domain/MediaLibraryStatistics.java
  4. 54
      airsonic-main/src/main/java/org/airsonic/player/service/MediaScannerService.java
  5. 25
      airsonic-main/src/main/java/org/airsonic/player/service/SettingsService.java
  6. 66
      airsonic-main/src/main/java/org/airsonic/player/service/search/IndexManager.java
  7. 5
      airsonic-main/src/main/java/org/airsonic/player/service/upnp/FolderBasedContentDirectory.java
  8. 8
      airsonic-main/src/main/java/org/airsonic/player/service/upnp/RootUpnpProcessor.java
  9. 41
      airsonic-main/src/main/java/org/airsonic/player/util/Util.java
  10. 6
      airsonic-main/src/test/java/org/airsonic/player/service/MediaScannerServiceTestCase.java
  11. 31
      airsonic-main/src/test/java/org/airsonic/player/service/MediaScannerServiceUnitTest.java
  12. 2
      airsonic-main/src/test/java/org/airsonic/player/service/search/IndexManagerTestCase.java
  13. 88
      airsonic-main/src/test/java/org/airsonic/player/util/UtilTest.java

@ -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);

@ -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...");

@ -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);
@NotNull
private Integer artistCount;
@NotNull
private Integer albumCount;
@NotNull
private Integer songCount;
@NotNull
private Long totalLengthInBytes;
@NotNull
private Long totalDurationInSeconds;
@NotNull
private Date scanDate;
private int artistCount;
private int albumCount;
private int songCount;
private long totalLengthInBytes;
private long totalDurationInSeconds;
public MediaLibraryStatistics() {
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;
}
public MediaLibraryStatistics() {
public MediaLibraryStatistics(Date scanDate) {
if (scanDate == null) {
throw new IllegalArgumentException();
}
this.scanDate = scanDate;
reset();
}
public void 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);
}
}

@ -51,8 +51,6 @@ public class MediaScannerService {
private static final Logger LOG = LoggerFactory.getLogger(MediaScannerService.class);
private MediaLibraryStatistics statistics;
private boolean scanning;
private ScheduledExecutorService scheduler;
@ -76,13 +74,11 @@ public class MediaScannerService {
@PostConstruct
public void init() {
indexManager.initializeIndexDirectory();
statistics = settingsService.getMediaLibraryStatistics();
schedule();
}
public void initNoSchedule() {
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);
// 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.
*/
@ -160,8 +160,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 {
@ -170,7 +171,6 @@ public class MediaScannerService {
Genres genres = new Genres();
scanCount = 0;
statistics.reset();
mediaFileService.setMemoryCacheEnabled(false);
indexManager.startIndexing();
@ -180,24 +180,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());
@ -208,21 +208,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) {
@ -241,22 +238,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());
@ -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) {
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

@ -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");
}
}
}

@ -178,6 +178,12 @@ public class MediaScannerServiceTestCase {
assertNotNull(mediaFile);
}
@Test
public void testNeverScanned() {
mediaScannerService.neverScanned();
}
@Test
public void testMusicBrainzReleaseIdTag() {

@ -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());
}
}

@ -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);
}
}
Loading…
Cancel
Save