diff --git a/airsonic-main/src/main/java/org/airsonic/player/controller/InternalHelpController.java b/airsonic-main/src/main/java/org/airsonic/player/controller/InternalHelpController.java index 150ceca1..f7f1a99a 100644 --- a/airsonic-main/src/main/java/org/airsonic/player/controller/InternalHelpController.java +++ b/airsonic-main/src/main/java/org/airsonic/player/controller/InternalHelpController.java @@ -20,6 +20,7 @@ package org.airsonic.player.controller; import org.airsonic.player.dao.DaoHelper; +import org.airsonic.player.dao.MediaFileDao; import org.airsonic.player.dao.MusicFolderDao; import org.airsonic.player.domain.MediaLibraryStatistics; import org.airsonic.player.domain.MusicFolder; @@ -162,6 +163,8 @@ public class InternalHelpController { @Autowired private MusicFolderDao musicFolderDao; @Autowired + private MediaFileDao mediaFileDao; + @Autowired private TranscodingService transcodingService; @GetMapping @@ -311,6 +314,12 @@ public class InternalHelpController { map.put("dbMediaFileDistinctArtistCount", daoHelper.getJdbcTemplate().queryForObject(String.format("SELECT count(DISTINCT artist) FROM MEDIA_FILE WHERE present"), Long.class)); map.put("dbMediaFileDistinctAlbumArtistCount", daoHelper.getJdbcTemplate().queryForObject(String.format("SELECT count(DISTINCT album_artist) FROM MEDIA_FILE WHERE present"), Long.class)); + map.put("dbMediaFilesInNonPresentMusicFoldersCount", mediaFileDao.getFilesInNonPresentMusicFoldersCount(Arrays.asList(settingsService.getPodcastFolder()))); + map.put("dbMediaFilesInNonPresentMusicFoldersSample", mediaFileDao.getFilesInNonPresentMusicFolders(10, Arrays.asList(settingsService.getPodcastFolder()))); + + map.put("dbMediaFilesWithMusicFolderMismatchCount", mediaFileDao.getFilesWithMusicFolderMismatchCount()); + map.put("dbMediaFilesWithMusicFolderMismatchSample", mediaFileDao.getFilesWithMusicFolderMismatch(10)); + map.put("dbTableCount", dbTableCount); } diff --git a/airsonic-main/src/main/java/org/airsonic/player/dao/MediaFileDao.java b/airsonic-main/src/main/java/org/airsonic/player/dao/MediaFileDao.java index 3b9f3313..64dc75e0 100644 --- a/airsonic-main/src/main/java/org/airsonic/player/dao/MediaFileDao.java +++ b/airsonic-main/src/main/java/org/airsonic/player/dao/MediaFileDao.java @@ -597,6 +597,67 @@ public class MediaFileDao extends AbstractDao { return namedQuery(query, rowMapper, args); } + /** + * Return a list of media file objects that don't belong to an existing music folder + * @param count maximum number of media file objects to return + * @param excludeFolders music folder paths excluded from the results + * @return a list of media files, sorted by id + */ + public List getFilesInNonPresentMusicFolders(final int count, List excludeFolders) { + Map args = new HashMap<>(); + args.put("excludeFolders", excludeFolders); + args.put("count", count); + return namedQuery( + "SELECT " + prefix(QUERY_COLUMNS, "media_file") + " FROM media_file " + + "LEFT OUTER JOIN music_folder ON music_folder.path = media_file.folder " + + "WHERE music_folder.id IS NULL " + + "AND media_file.folder NOT IN (:excludeFolders) " + + "ORDER BY media_file.id LIMIT :count", + rowMapper, args); + } + + /** + * Count the number of media files that don't belong to an existing music folder + * @param excludeFolders music folder paths excluded from the results + * @return a number of media file rows in the database + */ + public int getFilesInNonPresentMusicFoldersCount(List excludeFolders) { + Map args = new HashMap<>(); + args.put("excludeFolders", excludeFolders); + return namedQueryForInt( + "SELECT count(media_file.id) FROM media_file " + + "LEFT OUTER JOIN music_folder ON music_folder.path = media_file.folder " + + "WHERE music_folder.id IS NULL " + + "AND media_file.folder NOT IN (:excludeFolders) ", + 0, args); + } + + /** + * Return a list of media file objects whose path don't math their music folder + * @param count maximum number of media file objects to return + * @return a list of media files, sorted by id + */ + public List getFilesWithMusicFolderMismatch(final int count) { + return query( + "SELECT " + prefix(QUERY_COLUMNS, "media_file") + " FROM media_file " + + "WHERE media_file.path != media_file.folder " + + "AND media_file.path NOT LIKE concat(media_file.folder, '/%') " + + "ORDER BY media_file.id LIMIT ?", + rowMapper, count); + } + + /** + * Count the number of media files whose path don't math their music folder + * @return a number of media file rows in the database + */ + public int getFilesWithMusicFolderMismatchCount() { + return queryForInt( + "SELECT count(media_file.id) FROM media_file " + + "WHERE media_file.path != media_file.folder " + + "AND media_file.path NOT LIKE concat(media_file.folder, '/%')", + 0); + } + public int getAlbumCount(final List musicFolders) { if (musicFolders.isEmpty()) { return 0; diff --git a/airsonic-main/src/main/resources/org/airsonic/player/i18n/ResourceBundle_en.properties b/airsonic-main/src/main/resources/org/airsonic/player/i18n/ResourceBundle_en.properties index 6d6d7fbf..8ca7b80b 100644 --- a/airsonic-main/src/main/resources/org/airsonic/player/i18n/ResourceBundle_en.properties +++ b/airsonic-main/src/main/resources/org/airsonic/player/i18n/ResourceBundle_en.properties @@ -266,6 +266,7 @@ internalhelp.title=About {0} Internals internalhelp.details=Internal details internalhelp.statistics=Statistics internalhelp.database=Database +internalhalp.databaseconsistency=Database Consistency internalhelp.index=Search Index internalhelp.filesystem=Filesystem internalhelp.transcoding=Transcoding diff --git a/airsonic-main/src/main/webapp/WEB-INF/jsp/internalhelp.jsp b/airsonic-main/src/main/webapp/WEB-INF/jsp/internalhelp.jsp index 4b95ff11..e0327a6c 100644 --- a/airsonic-main/src/main/webapp/WEB-INF/jsp/internalhelp.jsp +++ b/airsonic-main/src/main/webapp/WEB-INF/jsp/internalhelp.jsp @@ -151,6 +151,65 @@

+

+ + +

+ + + + + + + + + + + + + +
+ + + OK + All media files in the database have a valid music folder. + + + Warning + The media file database contains files whose music folder is no longer present. Examples are below. + + +
dbMediaFilesInNonPresentMusicFoldersCount${model.dbMediaFilesInNonPresentMusicFoldersCount}
IDPATHFOLDER
${invalidFolderSample.id}${invalidFolderSample.path}${invalidFolderSample.folder}
+ +

+ + + + + + + + + + + + + +
+ + + OK + All media files in the database match their music folder. + + + Warning + The media file database contains files whose path does not match their music folder path. Examples are below. + + +
dbMediaFilesWithMusicFolderMismatchCount${model.dbMediaFilesWithMusicFolderMismatchCount}
IDPATHFOLDER
${mismatchSample.id}${mismatchSample.path}${mismatchSample.folder}
+ +

+