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 0cd99a37..150ceca1 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 @@ -21,9 +21,11 @@ package org.airsonic.player.controller; import org.airsonic.player.dao.DaoHelper; import org.airsonic.player.dao.MusicFolderDao; +import org.airsonic.player.domain.MediaLibraryStatistics; import org.airsonic.player.domain.MusicFolder; import org.airsonic.player.service.SecurityService; import org.airsonic.player.service.SettingsService; +import org.airsonic.player.service.TranscodingService; import org.airsonic.player.service.VersionService; import org.airsonic.player.service.search.AnalyzerFactory; import org.airsonic.player.service.search.IndexManager; @@ -64,12 +66,19 @@ public class InternalHelpController { private static final int LOG_LINES_TO_SHOW = 50; - public class MusicFolderStatistics { + public class FileStatistics { private String name; + private String path; private String freeFilesystemSizeBytes; private String totalFilesystemSizeBytes; private boolean readable; private boolean writable; + private boolean executable; + + public FileStatistics() {} + + public FileStatistics(File path) { + } public String getName() { return name; @@ -87,10 +96,18 @@ public class InternalHelpController { return writable; } + public boolean isExecutable() { + return executable; + } + public String getTotalFilesystemSizeBytes() { return totalFilesystemSizeBytes; } + public String getPath() { + return path; + } + public void setName(String name) { this.name = name; } @@ -107,9 +124,27 @@ public class InternalHelpController { this.writable = writable; } + public void setExecutable(boolean executable) { + this.executable = executable; + } + public void setTotalFilesystemSizeBytes(String totalFilesystemSizeBytes) { this.totalFilesystemSizeBytes = totalFilesystemSizeBytes; } + + public void setPath(String path) { + this.path = path; + } + + public void setFromFile(File file) { + this.setName(file.getName()); + this.setPath(file.getAbsolutePath()); + this.setFreeFilesystemSizeBytes(FileUtils.byteCountToDisplaySize(file.getUsableSpace())); + this.setTotalFilesystemSizeBytes(FileUtils.byteCountToDisplaySize(file.getTotalSpace())); + this.setReadable(Files.isReadable(file.toPath())); + this.setWritable(Files.isWritable(file.toPath())); + this.setExecutable(Files.isExecutable(file.toPath())); + } } @Autowired @@ -126,6 +161,8 @@ public class InternalHelpController { private AnalyzerFactory analyzerFactory; @Autowired private MusicFolderDao musicFolderDao; + @Autowired + private TranscodingService transcodingService; @GetMapping protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) { @@ -146,15 +183,44 @@ public class InternalHelpController { ", java " + System.getProperty("java.version") + ", " + System.getProperty("os.name"); + map.put("user", securityService.getCurrentUser(request)); + map.put("brand", settingsService.getBrand()); + map.put("localVersion", versionService.getLocalVersion()); + map.put("buildDate", versionService.getLocalBuildDate()); + map.put("buildNumber", versionService.getLocalBuildNumber()); + map.put("serverInfo", serverInfo); + map.put("usedMemory", totalMemory - freeMemory); + map.put("totalMemory", totalMemory); + File logFile = SettingsService.getLogFile(); + List latestLogEntries = getLatestLogEntries(logFile); + map.put("logEntries", latestLogEntries); + map.put("logFile", logFile); + + // Gather internal information + gatherScanInfo(map); + gatherIndexInfo(map); + gatherDatabaseInfo(map); + gatherFilesystemInfo(map); + gatherTranscodingInfo(map); + gatherLocaleInfo(map); + + return new ModelAndView("internalhelp","model",map); + } + + private void gatherScanInfo(Map map) { // Airsonic scan statistics - map.put("statAlbumCount", indexManager.getStatistics().getAlbumCount()); - map.put("statArtistCount", indexManager.getStatistics().getArtistCount()); - map.put("statSongCount", indexManager.getStatistics().getSongCount()); - map.put("statLastScanDate", indexManager.getStatistics().getScanDate()); - map.put("statTotalDurationSeconds", indexManager.getStatistics().getTotalDurationInSeconds()); - map.put("statTotalLengthBytes", FileUtils.byteCountToDisplaySize(indexManager.getStatistics().getTotalLengthInBytes())); - - // Lucene index statistics + MediaLibraryStatistics stats = indexManager.getStatistics(); + if (stats != null) { + map.put("statAlbumCount", stats.getAlbumCount()); + map.put("statArtistCount", stats.getArtistCount()); + map.put("statSongCount", stats.getSongCount()); + map.put("statLastScanDate", stats.getScanDate()); + map.put("statTotalDurationSeconds", stats.getTotalDurationInSeconds()); + map.put("statTotalLengthBytes", FileUtils.byteCountToDisplaySize(stats.getTotalLengthInBytes())); + } + } + + private void gatherIndexInfo(Map map) { try (IndexReader reader = indexManager.getSearcher(IndexType.SONG).getIndexReader()) { map.put("indexSongCount", reader.numDocs()); map.put("indexSongDeletedCount", reader.numDeletedDocs()); @@ -190,8 +256,21 @@ public class InternalHelpController { } catch (IOException e) { LOG.debug("Unable to gather information", e); } + } - // Database statistics + private void gatherLocaleInfo(Map map) { + map.put("localeDefault", Locale.getDefault()); + map.put("localeUserLanguage", System.getProperty("user.language")); + map.put("localeUserCountry", System.getProperty("user.country")); + map.put("localeFileEncoding", System.getProperty("file.encoding")); + map.put("localeSunJnuEncoding", System.getProperty("sun.jnu.encoding")); + map.put("localeSunIoUnicodeEncoding", System.getProperty("sun.io.unicode.encoding")); + map.put("localeLang", System.getenv("LANG")); + map.put("localeLcAll", System.getenv("LC_ALL")); + map.put("localeDefaultCharset", Charset.defaultCharset()); + } + + private void gatherDatabaseInfo(Map map) { try (Connection conn = daoHelper.getDataSource().getConnection()) { map.put("dbDriverName", conn.getMetaData().getDriverName()); map.put("dbDriverVersion", conn.getMetaData().getDriverVersion()); @@ -233,51 +312,28 @@ public class InternalHelpController { map.put("dbMediaFileDistinctAlbumArtistCount", daoHelper.getJdbcTemplate().queryForObject(String.format("SELECT count(DISTINCT album_artist) FROM MEDIA_FILE WHERE present"), Long.class)); map.put("dbTableCount", dbTableCount); + } - // Filesystem statistics + private void gatherFilesystemInfo(Map map) { map.put("fsHomeDirectorySizeBytes", FileUtils.sizeOfDirectory(settingsService.getAirsonicHome())); map.put("fsHomeDirectorySize", FileUtils.byteCountToDisplaySize((long)map.get("fsHomeDirectorySizeBytes"))); map.put("fsHomeTotalSpaceBytes", settingsService.getAirsonicHome().getTotalSpace()); map.put("fsHomeTotalSpace", FileUtils.byteCountToDisplaySize((long)map.get("fsHomeTotalSpaceBytes"))); map.put("fsHomeUsableSpaceBytes", settingsService.getAirsonicHome().getUsableSpace()); map.put("fsHomeUsableSpace", FileUtils.byteCountToDisplaySize((long)map.get("fsHomeUsableSpaceBytes"))); - SortedMap fsMusicFolderStatistics = new TreeMap<>(); + SortedMap fsMusicFolderStatistics = new TreeMap<>(); for (MusicFolder folder: musicFolderDao.getAllMusicFolders()) { - MusicFolderStatistics stat = new MusicFolderStatistics(); + FileStatistics stat = new FileStatistics(); + stat.setFromFile(folder.getPath()); stat.setName(folder.getName()); - stat.setFreeFilesystemSizeBytes(FileUtils.byteCountToDisplaySize(folder.getPath().getUsableSpace())); - stat.setTotalFilesystemSizeBytes(FileUtils.byteCountToDisplaySize(folder.getPath().getTotalSpace())); - stat.setReadable(Files.isReadable(folder.getPath().toPath())); - stat.setWritable(Files.isWritable(folder.getPath().toPath())); fsMusicFolderStatistics.put(folder.getName(), stat); } map.put("fsMusicFolderStatistics", fsMusicFolderStatistics); + } - // OS information - map.put("localeDefault", Locale.getDefault()); - map.put("localeUserLanguage", System.getProperty("user.language")); - map.put("localeUserCountry", System.getProperty("user.country")); - map.put("localeFileEncoding", System.getProperty("file.encoding")); - map.put("localeSunJnuEncoding", System.getProperty("sun.jnu.encoding")); - map.put("localeSunIoUnicodeEncoding", System.getProperty("sun.io.unicode.encoding")); - map.put("localeLang", System.getenv("LANG")); - map.put("localeLcAll", System.getenv("LC_ALL")); - map.put("localeDefaultCharset", Charset.defaultCharset()); - - map.put("user", securityService.getCurrentUser(request)); - map.put("brand", settingsService.getBrand()); - map.put("localVersion", versionService.getLocalVersion()); - map.put("buildDate", versionService.getLocalBuildDate()); - map.put("buildNumber", versionService.getLocalBuildNumber()); - map.put("serverInfo", serverInfo); - map.put("usedMemory", totalMemory - freeMemory); - map.put("totalMemory", totalMemory); - File logFile = SettingsService.getLogFile(); - List latestLogEntries = getLatestLogEntries(logFile); - map.put("logEntries", latestLogEntries); - map.put("logFile", logFile); - - return new ModelAndView("internalhelp","model",map); + private void gatherTranscodingInfo(Map map) { + map.put("fsFfprobeInfo", gatherStatisticsForTranscodingExecutable("ffprobe")); + map.put("fsFfmpegInfo", gatherStatisticsForTranscodingExecutable("ffmpeg")); } private static List getLatestLogEntries(File logFile) { @@ -297,5 +353,38 @@ public class InternalHelpController { } } + private File lookForExecutable(String executableName) { + for (String path : System.getenv("PATH").split(File.pathSeparator)) { + File file = new File(path, executableName); + if (file.exists()) { + LOG.debug("Found {} in {}", executableName, path); + return file; + } else { + LOG.debug("Looking for {} in {} (not found)", executableName, path); + } + } + return null; + } + + private File lookForTranscodingExecutable(String executableName) { + File executableLocation = null; + for (String name: Arrays.asList(executableName, String.format("%s.exe", executableName))) { + executableLocation = new File(transcodingService.getTranscodeDirectory(), name); + if (executableLocation.exists()) return executableLocation; + executableLocation = lookForExecutable(executableName); + if (executableLocation.exists()) return executableLocation; + } + return null; + } + + private FileStatistics gatherStatisticsForTranscodingExecutable(String executableName) { + FileStatistics executableStatistics = null; + File executableLocation = lookForTranscodingExecutable(executableName); + if (executableLocation != null) { + executableStatistics = new FileStatistics(); + executableStatistics.setFromFile(executableLocation); + } + return executableStatistics; + } } 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 e4da26d9..6d6d7fbf 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 @@ -268,6 +268,7 @@ internalhelp.statistics=Statistics internalhelp.database=Database internalhelp.index=Search Index internalhelp.filesystem=Filesystem +internalhelp.transcoding=Transcoding internalhelp.musicfolders=Music Folders internalhelp.locale=Locale internalhelp.defaultlocale=Default locale 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 87307e49..4b95ff11 100644 --- a/airsonic-main/src/main/webapp/WEB-INF/jsp/internalhelp.jsp +++ b/airsonic-main/src/main/webapp/WEB-INF/jsp/internalhelp.jsp @@ -182,6 +182,50 @@

+

+ + +

+ + + + + + + + + + + + + + +
+ + + OK + Ffprobe appears configured correctly. + + + Warning + Ffprobe is either missing or not executable. Transcoding may not work properly. + + +
Ffprobe path${model.fsFfprobeInfo.path}
Ffprobe is readable?${model.fsFfprobeInfo.readable}
Ffprobe is executable?${model.fsFfprobeInfo.executable}
+ + + OK + Ffmpeg appears configured correctly. + + + Warning + Ffmpeg is either missing or not executable. Transcoding may not work properly. + + +
Ffmpeg path${model.fsFfmpegInfo.path}
Ffmpeg is readable?${model.fsFfmpegInfo.readable}
Ffmpeg is executable?${model.fsFfmpegInfo.executable}
+ +

+