MediaScannerService unit test and test media library
(cherry picked from commit c2b17f0) Signed-off-by: Rémi Cocula <rcocula@gmail.com>
This commit is contained in:
@@ -26,6 +26,15 @@
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Metrics -->
|
||||
<dependency>
|
||||
<groupId>io.dropwizard.metrics</groupId>
|
||||
<artifactId>metrics-core</artifactId>
|
||||
<version>${metrics.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-webmvc</artifactId>
|
||||
|
||||
@@ -70,6 +70,11 @@ public class MediaScannerService {
|
||||
schedule();
|
||||
}
|
||||
|
||||
public void initNoSchedule() {
|
||||
deleteOldIndexFiles();
|
||||
statistics = settingsService.getMediaLibraryStatistics();
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule background execution of media library scanning.
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
package org.libresonic.player;
|
||||
|
||||
import org.libresonic.player.dao.DaoHelper;
|
||||
import org.libresonic.player.service.MediaScannerService;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.support.ClassPathXmlApplicationContext;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class TestCaseUtils {
|
||||
|
||||
/**
|
||||
* Constructs a map of records count per table.
|
||||
*
|
||||
* @param daoHelper DaoHelper object
|
||||
* @return Map table name -> records count
|
||||
*/
|
||||
public static Map<String, Integer> recordsInAllTables(DaoHelper daoHelper) {
|
||||
List<String> tableNames = daoHelper.getJdbcTemplate().queryForList("" +
|
||||
"select table_name " +
|
||||
"from information_schema.system_tables " +
|
||||
"where table_name not like 'SYSTEM%'"
|
||||
, String.class);
|
||||
Map<String, Integer> nbRecords =
|
||||
tableNames.stream()
|
||||
.collect(Collectors.toMap(table -> table, table -> recordsInTable(table,daoHelper)));
|
||||
|
||||
return nbRecords;
|
||||
}
|
||||
|
||||
/**
|
||||
* Counts records in a table.
|
||||
*
|
||||
* @param tableName
|
||||
* @param daoHelper
|
||||
* @return
|
||||
*/
|
||||
public static Integer recordsInTable(String tableName, DaoHelper daoHelper) {
|
||||
return daoHelper.getJdbcTemplate().queryForInt("select count(1) from " + tableName);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Constructs the path of a resource according to the path of the current class.
|
||||
*
|
||||
* @param baseResources
|
||||
* @return
|
||||
*/
|
||||
private static String basePath(String baseResources) {
|
||||
String basePath = TestCaseUtils.class.getResource(baseResources).toString();
|
||||
if (basePath.startsWith("file:")) {
|
||||
return TestCaseUtils.class.getResource(baseResources).toString().replace("file:","");
|
||||
}
|
||||
return basePath;
|
||||
}
|
||||
|
||||
|
||||
public static void setLibresonicHome(String baseResources) {
|
||||
String subsoncicHome = basePath(baseResources);
|
||||
System.setProperty("libresonic.home",subsoncicHome);
|
||||
}
|
||||
|
||||
public static ApplicationContext loadSpringApplicationContext(String baseResources) {
|
||||
String applicationContextService = baseResources + "applicationContext-service.xml";
|
||||
String applicationContextCache = baseResources + "applicationContext-cache.xml";
|
||||
|
||||
String[] configLocations = new String[]{
|
||||
TestCaseUtils.class.getClass().getResource(applicationContextCache).toString(),
|
||||
TestCaseUtils.class.getClass().getResource(applicationContextService).toString()
|
||||
};
|
||||
return new ClassPathXmlApplicationContext(configLocations);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Scans the music library * @param mediaScannerService
|
||||
*/
|
||||
public static void execScan(MediaScannerService mediaScannerService) {
|
||||
mediaScannerService.scanLibrary();
|
||||
|
||||
while (mediaScannerService.isScanning()) {
|
||||
try {
|
||||
Thread.sleep(1000);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package org.libresonic.player.dao;
|
||||
|
||||
import org.libresonic.player.domain.MusicFolder;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
public class MusicFolderDaoMock extends MusicFolderDao {
|
||||
|
||||
private static String baseResources = "/MEDIAS/";
|
||||
|
||||
public static String resolveBaseMediaPath() {
|
||||
String baseDir = MusicFolderDaoMock.class.getResource(baseResources).toString().replace("file:","");
|
||||
return baseDir;
|
||||
}
|
||||
|
||||
public static String resolveMusicFolderPath() {
|
||||
return (MusicFolderDaoMock.resolveBaseMediaPath() + "Music");
|
||||
}
|
||||
|
||||
public static String resolveMusic2FolderPath() {
|
||||
return (MusicFolderDaoMock.resolveBaseMediaPath() + "Music2");
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<MusicFolder> getAllMusicFolders() {
|
||||
List<MusicFolder> liste = new ArrayList<>();
|
||||
File musicDir = new File(MusicFolderDaoMock.resolveMusicFolderPath());
|
||||
MusicFolder musicFolder = new MusicFolder(1,musicDir,"Music",true,new Date());
|
||||
liste.add(musicFolder);
|
||||
|
||||
File music2Dir = new File(MusicFolderDaoMock.resolveMusic2FolderPath());
|
||||
MusicFolder musicFolder2 = new MusicFolder(2,music2Dir,"Music2",true,new Date());
|
||||
liste.add(musicFolder2);
|
||||
return liste;
|
||||
}
|
||||
}
|
||||
+141
@@ -0,0 +1,141 @@
|
||||
package org.libresonic.player.service;
|
||||
|
||||
import com.codahale.metrics.ConsoleReporter;
|
||||
import com.codahale.metrics.JmxReporter;
|
||||
import com.codahale.metrics.MetricRegistry;
|
||||
import com.codahale.metrics.Timer;
|
||||
import junit.framework.Assert;
|
||||
import junit.framework.TestCase;
|
||||
import org.libresonic.player.TestCaseUtils;
|
||||
import org.libresonic.player.dao.*;
|
||||
import org.libresonic.player.domain.Album;
|
||||
import org.libresonic.player.domain.Artist;
|
||||
import org.libresonic.player.domain.MediaFile;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* A unit test class to test the MediaScannerService.
|
||||
*
|
||||
* This class uses the Spring application context configuration present in the
|
||||
* /org/libresonic/player/service/mediaScannerServiceTestCase/ directory.
|
||||
*
|
||||
* The media library is found in the /MEDIAS directory.
|
||||
* It is composed of 2 musicFolders (Music and Music2) and several little weight audio files.
|
||||
*
|
||||
* At runtime, the subsonic_home dir is set to target/test-classes/org/libresonic/player/service/mediaScannerServiceTestCase.
|
||||
* An empty database is created on the fly.
|
||||
*
|
||||
*/
|
||||
public class MediaScannerServiceTestCase extends TestCase {
|
||||
|
||||
private static String baseResources = "/org/libresonic/player/service/mediaScannerServiceTestCase/";
|
||||
|
||||
private final MetricRegistry metrics = new MetricRegistry();
|
||||
|
||||
|
||||
private MediaScannerService mediaScannerService = null;
|
||||
private MediaFileDao mediaFileDao = null;
|
||||
private MusicFolderDao musicFolderDao = null;
|
||||
private DaoHelper daoHelper = null;
|
||||
private MediaFileService mediaFileService = null;
|
||||
private ArtistDao artistDao = null;
|
||||
private AlbumDao albumDao = null;
|
||||
|
||||
|
||||
@Override
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
|
||||
TestCaseUtils.setLibresonicHome(baseResources);
|
||||
|
||||
// load spring context
|
||||
ApplicationContext context = TestCaseUtils.loadSpringApplicationContext(baseResources);
|
||||
|
||||
mediaScannerService = (MediaScannerService)context.getBean("mediaScannerService");
|
||||
mediaFileDao = (MediaFileDao)context.getBean("mediaFileDao");
|
||||
musicFolderDao = (MusicFolderDao) context.getBean("musicFolderDao");
|
||||
daoHelper = (DaoHelper) context.getBean("daoHelper");
|
||||
mediaFileService = (MediaFileService) context.getBean("mediaFileService");
|
||||
artistDao = (ArtistDao) context.getBean("artistDao");
|
||||
albumDao = (AlbumDao) context.getBean("albumDao");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Tests the MediaScannerService by scanning the test media library into an empty database.
|
||||
*/
|
||||
public void testScanLibrary() {
|
||||
|
||||
//startMetricsReport();
|
||||
|
||||
|
||||
Timer globalTimer = metrics.timer(MetricRegistry.name(MediaScannerServiceTestCase.class, "Timer.global"));
|
||||
|
||||
Timer.Context globalTimerContext = globalTimer.time();
|
||||
TestCaseUtils.execScan(mediaScannerService);
|
||||
globalTimerContext.stop();
|
||||
|
||||
System.out.println("--- Report of records count per table ---");
|
||||
Map<String,Integer> records = TestCaseUtils.recordsInAllTables(daoHelper);
|
||||
records.keySet().forEach(tableName -> System.out.println(tableName+" : "+records.get(tableName).toString() ));
|
||||
System.out.println("--- *********************** ---");
|
||||
|
||||
|
||||
// Music Folder Music must have 3 children
|
||||
List<MediaFile> listeMusicChildren = mediaFileDao.getChildrenOf(MusicFolderDaoMock.resolveMusicFolderPath());
|
||||
Assert.assertEquals(3,listeMusicChildren.size());
|
||||
// Music Folder Music2 must have 1 children
|
||||
List<MediaFile> listeMusic2Children = mediaFileDao.getChildrenOf(MusicFolderDaoMock.resolveMusic2FolderPath());
|
||||
Assert.assertEquals(1,listeMusic2Children.size());
|
||||
|
||||
System.out.println("--- List of all artists ---");
|
||||
System.out.println("artistName#albumCount");
|
||||
List<Artist> allArtists = artistDao.getAlphabetialArtists(0,0,musicFolderDao.getAllMusicFolders());
|
||||
allArtists.forEach(artist -> System.out.println(artist.getName()+"#"+artist.getAlbumCount()));
|
||||
System.out.println("--- *********************** ---");
|
||||
|
||||
System.out.println("--- List of all albums ---");
|
||||
System.out.println("name#artist");
|
||||
List<Album> allAlbums = albumDao.getAlphabetialAlbums(0,0,true,musicFolderDao.getAllMusicFolders());
|
||||
allAlbums.forEach(album -> System.out.println(album.getName()+"#"+album.getArtist()));
|
||||
Assert.assertEquals(5,allAlbums.size());
|
||||
System.out.println("--- *********************** ---");
|
||||
|
||||
|
||||
|
||||
List<MediaFile> listeSongs = mediaFileDao.getSongsByGenre("Baroque Instrumental",0,0,musicFolderDao.getAllMusicFolders());
|
||||
|
||||
// display out metrics report
|
||||
ConsoleReporter reporter = ConsoleReporter.forRegistry(metrics)
|
||||
.convertRatesTo(TimeUnit.SECONDS.SECONDS)
|
||||
.convertDurationsTo(TimeUnit.MILLISECONDS)
|
||||
.build();
|
||||
reporter.report();
|
||||
|
||||
System.out.print("End");
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a Metrics Console and JMX reporter.
|
||||
*/
|
||||
private void startMetricsReport() {
|
||||
|
||||
// Reporter console
|
||||
ConsoleReporter reporter = ConsoleReporter.forRegistry(metrics)
|
||||
.convertRatesTo(TimeUnit.SECONDS.SECONDS)
|
||||
.convertDurationsTo(TimeUnit.MILLISECONDS)
|
||||
.build();
|
||||
reporter.start(10, TimeUnit.SECONDS);
|
||||
|
||||
// Jmx reporter
|
||||
final JmxReporter jmxReporter = JmxReporter.forRegistry(metrics).build();
|
||||
jmxReporter.start();
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
|
After Width: | Height: | Size: 39 KiB |
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
|
After Width: | Height: | Size: 47 KiB |
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
|
After Width: | Height: | Size: 70 KiB |
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
|
After Width: | Height: | Size: 51 KiB |
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 32 KiB |
+17
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="ISO-8859-1"?>
|
||||
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
|
||||
|
||||
<bean id="cacheFactory" class="org.libresonic.player.cache.CacheFactory"/>
|
||||
|
||||
<bean id="userCache" factory-bean="cacheFactory" factory-method="getCache">
|
||||
<constructor-arg value="userCache"/>
|
||||
</bean>
|
||||
|
||||
<bean id="mediaFileMemoryCache" factory-bean="cacheFactory" factory-method="getCache">
|
||||
<constructor-arg value="mediaFileMemoryCache"/>
|
||||
</bean>
|
||||
|
||||
</beans>
|
||||
+186
@@ -0,0 +1,186 @@
|
||||
<?xml version="1.0" encoding="ISO-8859-1"?>
|
||||
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
|
||||
|
||||
|
||||
<!-- DAO's -->
|
||||
|
||||
<bean id="playerDao" class="org.libresonic.player.dao.PlayerDao">
|
||||
<property name="daoHelper" ref="daoHelper"/>
|
||||
</bean>
|
||||
|
||||
<bean id="mediaFileDao" class="org.libresonic.player.dao.MediaFileDao">
|
||||
<property name="daoHelper" ref="daoHelper"/>
|
||||
</bean>
|
||||
|
||||
<bean id="artistDao" class="org.libresonic.player.dao.ArtistDao">
|
||||
<property name="daoHelper" ref="daoHelper"/>
|
||||
</bean>
|
||||
|
||||
<bean id="albumDao" class="org.libresonic.player.dao.AlbumDao">
|
||||
<property name="daoHelper" ref="daoHelper"/>
|
||||
</bean>
|
||||
|
||||
<bean id="playlistDao" class="org.libresonic.player.dao.PlaylistDao">
|
||||
<property name="daoHelper" ref="daoHelper"/>
|
||||
</bean>
|
||||
|
||||
<bean id="playQueueDao" class="org.libresonic.player.dao.PlayQueueDao">
|
||||
<property name="daoHelper" ref="daoHelper"/>
|
||||
</bean>
|
||||
|
||||
<bean id="internetRadioDao" class="org.libresonic.player.dao.InternetRadioDao">
|
||||
<property name="daoHelper" ref="daoHelper"/>
|
||||
</bean>
|
||||
|
||||
<bean id="musicFileInfoDao" class="org.libresonic.player.dao.RatingDao">
|
||||
<property name="daoHelper" ref="daoHelper"/>
|
||||
</bean>
|
||||
|
||||
<bean id="musicFolderDao" class="org.libresonic.player.dao.MusicFolderDaoMock">
|
||||
<property name="daoHelper" ref="daoHelper"/>
|
||||
</bean>
|
||||
|
||||
<bean id="userDao" class="org.libresonic.player.dao.UserDao">
|
||||
<property name="daoHelper" ref="daoHelper"/>
|
||||
</bean>
|
||||
|
||||
<bean id="transcodingDao" class="org.libresonic.player.dao.TranscodingDao">
|
||||
<property name="daoHelper" ref="daoHelper"/>
|
||||
</bean>
|
||||
|
||||
<bean id="podcastDao" class="org.libresonic.player.dao.PodcastDao">
|
||||
<property name="daoHelper" ref="daoHelper"/>
|
||||
</bean>
|
||||
|
||||
<bean id="avatarDao" class="org.libresonic.player.dao.AvatarDao">
|
||||
<property name="daoHelper" ref="daoHelper"/>
|
||||
</bean>
|
||||
|
||||
<bean id="shareDao" class="org.libresonic.player.dao.ShareDao">
|
||||
<property name="daoHelper" ref="daoHelper"/>
|
||||
</bean>
|
||||
|
||||
<bean id="bookmarkDao" class="org.libresonic.player.dao.BookmarkDao">
|
||||
<property name="daoHelper" ref="daoHelper"/>
|
||||
</bean>
|
||||
|
||||
<bean id="daoHelper" class="org.libresonic.player.dao.DaoHelperFactory" factory-method="create"/>
|
||||
|
||||
|
||||
<!-- Services -->
|
||||
|
||||
<bean id="mediaFileService" class="org.libresonic.player.service.MediaFileService">
|
||||
<property name="securityService" ref="securityService"/>
|
||||
<property name="settingsService" ref="settingsService"/>
|
||||
<property name="mediaFileMemoryCache" ref="mediaFileMemoryCache"/>
|
||||
<property name="mediaFileDao" ref="mediaFileDao"/>
|
||||
<property name="albumDao" ref="albumDao"/>
|
||||
<property name="metaDataParserFactory" ref="metaDataParserFactory"/>
|
||||
</bean>
|
||||
|
||||
<bean id="securityService" class="org.libresonic.player.service.SecurityService">
|
||||
<property name="settingsService" ref="settingsService"/>
|
||||
<property name="userDao" ref="userDao"/>
|
||||
<property name="userCache" ref="userCache"/>
|
||||
</bean>
|
||||
|
||||
<bean id="settingsService" class="org.libresonic.player.service.SettingsService" init-method="init">
|
||||
<property name="internetRadioDao" ref="internetRadioDao"/>
|
||||
<property name="musicFolderDao" ref="musicFolderDao"/>
|
||||
<property name="userDao" ref="userDao"/>
|
||||
<property name="avatarDao" ref="avatarDao"/>
|
||||
<property name="versionService" ref="versionService"/>
|
||||
</bean>
|
||||
|
||||
<bean id="mediaScannerService" class="org.libresonic.player.service.MediaScannerService" init-method="initNoSchedule" depends-on="metaDataParserFactory">
|
||||
<property name="settingsService" ref="settingsService"/>
|
||||
<property name="mediaFileService" ref="mediaFileService"/>
|
||||
<property name="mediaFileDao" ref="mediaFileDao"/>
|
||||
<property name="playlistService" ref="playlistService"/>
|
||||
<property name="artistDao" ref="artistDao"/>
|
||||
<property name="albumDao" ref="albumDao"/>
|
||||
<property name="searchService" ref="searchService"/>
|
||||
<!-- <property name="queueSender" ref="queueSender"/> -->
|
||||
</bean>
|
||||
|
||||
|
||||
<bean id="searchService" class="org.libresonic.player.service.SearchService">
|
||||
<property name="mediaFileService" ref="mediaFileService"/>
|
||||
<property name="artistDao" ref="artistDao"/>
|
||||
<property name="albumDao" ref="albumDao"/>
|
||||
</bean>
|
||||
|
||||
<bean id="networkService" class="org.libresonic.player.service.NetworkService" init-method="init">
|
||||
<property name="settingsService" ref="settingsService"/>
|
||||
</bean>
|
||||
|
||||
<bean id="playerService" class="org.libresonic.player.service.PlayerService" init-method="init">
|
||||
<property name="playerDao" ref="playerDao"/>
|
||||
<property name="statusService" ref="statusService"/>
|
||||
<property name="securityService" ref="securityService"/>
|
||||
<property name="transcodingService" ref="transcodingService"/>
|
||||
</bean>
|
||||
|
||||
<bean id="playlistService" class="org.libresonic.player.service.PlaylistService">
|
||||
<property name="mediaFileService" ref="mediaFileService"/>
|
||||
<property name="securityService" ref="securityService"/>
|
||||
<property name="settingsService" ref="settingsService"/>
|
||||
<property name="mediaFileDao" ref="mediaFileDao"/>
|
||||
<property name="playlistDao" ref="playlistDao"/>
|
||||
</bean>
|
||||
|
||||
<bean id="versionService" class="org.libresonic.player.service.VersionService" init-method="init"/>
|
||||
|
||||
<bean id="statusService" class="org.libresonic.player.service.StatusService">
|
||||
<property name="mediaFileService" ref="mediaFileService"/>
|
||||
</bean>
|
||||
|
||||
<bean id="ratingService" class="org.libresonic.player.service.RatingService">
|
||||
<property name="ratingDao" ref="musicFileInfoDao"/>
|
||||
<property name="mediaFileService" ref="mediaFileService"/>
|
||||
<property name="securityService" ref="securityService"/>
|
||||
</bean>
|
||||
|
||||
<bean id="musicIndexService" class="org.libresonic.player.service.MusicIndexService">
|
||||
<property name="settingsService" ref="settingsService"/>
|
||||
<property name="mediaFileService" ref="mediaFileService"/>
|
||||
</bean>
|
||||
|
||||
<bean id="transcodingService" class="org.libresonic.player.service.TranscodingService">
|
||||
<property name="transcodingDao" ref="transcodingDao"/>
|
||||
<property name="settingsService" ref="settingsService"/>
|
||||
</bean>
|
||||
|
||||
|
||||
<bean id="podcastService" class="org.libresonic.player.service.PodcastService" init-method="init">
|
||||
<property name="podcastDao" ref="podcastDao"/>
|
||||
<property name="settingsService" ref="settingsService"/>
|
||||
<property name="securityService" ref="securityService"/>
|
||||
<property name="mediaFileService" ref="mediaFileService"/>
|
||||
<property name="metaDataParserFactory" ref="metaDataParserFactory"/>
|
||||
</bean>
|
||||
|
||||
|
||||
|
||||
<bean id="metaDataParserFactory" class="org.libresonic.player.service.metadata.MetaDataParserFactory">
|
||||
<property name="parsers">
|
||||
<list>
|
||||
<bean class="org.libresonic.player.service.metadata.JaudiotaggerParser"/>
|
||||
<bean class="org.libresonic.player.service.metadata.FFmpegParser">
|
||||
<property name="transcodingService" ref="transcodingService"/>
|
||||
</bean>
|
||||
<bean class="org.libresonic.player.service.metadata.DefaultMetaDataParser"/>
|
||||
</list>
|
||||
</property>
|
||||
</bean>
|
||||
|
||||
|
||||
<bean id="localeResolver" class="org.libresonic.player.i18n.LibresonicLocaleResolver">
|
||||
<property name="securityService" ref="securityService"/>
|
||||
<property name="settingsService" ref="settingsService"/>
|
||||
</bean>
|
||||
|
||||
</beans>
|
||||
Reference in New Issue
Block a user