diff --git a/airsonic-main/src/main/java/org/airsonic/player/command/MusicFolderSettingsCommand.java b/airsonic-main/src/main/java/org/airsonic/player/command/MusicFolderSettingsCommand.java index 2e233ce1..5d315f8d 100644 --- a/airsonic-main/src/main/java/org/airsonic/player/command/MusicFolderSettingsCommand.java +++ b/airsonic-main/src/main/java/org/airsonic/player/command/MusicFolderSettingsCommand.java @@ -41,6 +41,8 @@ public class MusicFolderSettingsCommand { private boolean organizeByFolderStructure; private List musicFolders; private MusicFolderInfo newMusicFolder; + private String excludePatternString; + private boolean ignoreSymLinks; public String getInterval() { return interval; @@ -98,6 +100,22 @@ public class MusicFolderSettingsCommand { this.organizeByFolderStructure = organizeByFolderStructure; } + public String getExcludePatternString() { + return excludePatternString; + } + + public void setExcludePatternString(String excludePatternString) { + this.excludePatternString = excludePatternString; + } + + public boolean getIgnoreSymLinks() { + return ignoreSymLinks; + } + + public void setIgnoreSymLinks(boolean ignoreSymLinks) { + this.ignoreSymLinks = ignoreSymLinks; + } + public static class MusicFolderInfo { private Integer id; @@ -176,4 +194,4 @@ public class MusicFolderSettingsCommand { return existing; } } -} \ No newline at end of file +} diff --git a/airsonic-main/src/main/java/org/airsonic/player/controller/MusicFolderSettingsController.java b/airsonic-main/src/main/java/org/airsonic/player/controller/MusicFolderSettingsController.java index 4a705c4b..13f948b7 100644 --- a/airsonic-main/src/main/java/org/airsonic/player/controller/MusicFolderSettingsController.java +++ b/airsonic-main/src/main/java/org/airsonic/player/controller/MusicFolderSettingsController.java @@ -85,6 +85,8 @@ public class MusicFolderSettingsController { command.setScanning(mediaScannerService.isScanning()); command.setMusicFolders(wrap(settingsService.getAllMusicFolders(true, true))); command.setNewMusicFolder(new MusicFolderSettingsCommand.MusicFolderInfo()); + command.setExcludePatternString(settingsService.getExcludePatternString()); + command.setIgnoreSymLinks(settingsService.getIgnoreSymLinks()); model.addAttribute("command",command); } @@ -123,6 +125,8 @@ public class MusicFolderSettingsController { settingsService.setIndexCreationHour(Integer.parseInt(command.getHour())); settingsService.setFastCacheEnabled(command.isFastCache()); settingsService.setOrganizeByFolderStructure(command.isOrganizeByFolderStructure()); + settingsService.setExcludePatternString(command.getExcludePatternString()); + settingsService.setIgnoreSymLinks(command.getIgnoreSymLinks()); settingsService.save(); @@ -133,4 +137,4 @@ public class MusicFolderSettingsController { return "redirect:musicFolderSettings.view"; } -} \ No newline at end of file +} diff --git a/airsonic-main/src/main/java/org/airsonic/player/service/MediaFileService.java b/airsonic-main/src/main/java/org/airsonic/player/service/MediaFileService.java index 6d9ad1e8..fe173b95 100644 --- a/airsonic-main/src/main/java/org/airsonic/player/service/MediaFileService.java +++ b/airsonic-main/src/main/java/org/airsonic/player/service/MediaFileService.java @@ -38,6 +38,7 @@ import org.springframework.stereotype.Service; import java.io.File; import java.io.IOException; +import java.nio.file.Files; import java.util.*; /** @@ -451,9 +452,17 @@ public class MediaFileService { * @return Whether the child file is excluded. */ private boolean isExcluded(File file) { + if (settingsService.getIgnoreSymLinks() && Files.isSymbolicLink(file.toPath())) { + LOG.info("excluding symbolic link " + file.toPath()); + return true; + } + String name = file.getName(); + if (settingsService.getExcludePattern() != null && settingsService.getExcludePattern().matcher(name).find()) { + LOG.info("excluding file which matches exclude pattern " + settingsService.getExcludePatternString() + ": " + file.toPath()); + return true; + } // Exclude all hidden files starting with a single "." or "@eaDir" (thumbnail dir created on Synology devices). - String name = file.getName(); return (name.startsWith(".") && !name.startsWith("..")) || name.startsWith("@eaDir") || name.equals("Thumbs.db"); } diff --git a/airsonic-main/src/main/java/org/airsonic/player/service/SettingsService.java b/airsonic-main/src/main/java/org/airsonic/player/service/SettingsService.java index 3a254819..15aa86f7 100644 --- a/airsonic-main/src/main/java/org/airsonic/player/service/SettingsService.java +++ b/airsonic-main/src/main/java/org/airsonic/player/service/SettingsService.java @@ -42,6 +42,7 @@ import java.io.InputStream; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; +import java.util.regex.Pattern; /** @@ -113,6 +114,8 @@ public class SettingsService { private static final String KEY_SMTP_PASSWORD = "SmtpPassword"; private static final String KEY_SMTP_FROM = "SmtpFrom"; private static final String KEY_EXPORT_PLAYLIST_FORMAT = "PlaylistExportFormat"; + private static final String KEY_IGNORE_SYMLINKS = "IgnoreSymLinks"; + private static final String KEY_EXCLUDE_PATTERN_STRING = "ExcludePattern"; // Database Settings private static final String KEY_DATABASE_CONFIG_TYPE = "DatabaseConfigType"; @@ -180,6 +183,8 @@ public class SettingsService { private static final String DEFAULT_SONOS_SERVICE_NAME = "Airsonic"; private static final int DEFAULT_SONOS_SERVICE_ID = 242; private static final String DEFAULT_EXPORT_PLAYLIST_FORMAT = "m3u"; + private static final boolean DEFAULT_IGNORE_SYMLINKS = false; + private static final String DEFAULT_EXCLUDE_PATTERN_STRING = null; private static final String DEFAULT_SMTP_SERVER = null; private static final String DEFAULT_SMTP_ENCRYPTION = "None"; @@ -200,7 +205,7 @@ public class SettingsService { // Array of obsolete keys. Used to clean property file. private static final List OBSOLETE_KEYS = Arrays.asList("PortForwardingPublicPort", "PortForwardingLocalPort", "DownsamplingCommand", "DownsamplingCommand2", "DownsamplingCommand3", "AutoCoverBatch", "MusicMask", - "VideoMask", "CoverArtMask, HlsCommand", "HlsCommand2", "JukeboxCommand", + "VideoMask", "CoverArtMask, HlsCommand", "HlsCommand2", "JukeboxCommand", "CoverArtFileTypes", "UrlRedirectCustomHost", "CoverArtLimit", "StreamPort", "PortForwardingEnabled", "RewriteUrl", "UrlRedirectCustomUrl", "UrlRedirectContextPath", "UrlRedirectFrom", "UrlRedirectionEnabled", "UrlRedirectType", "Port", "HttpsPort", @@ -233,6 +238,8 @@ public class SettingsService { private List cachedMusicFolders; private final ConcurrentMap> cachedMusicFoldersPerUser = new ConcurrentHashMap<>(); + private Pattern excludePattern; + private void removeObsoleteProperties() { OBSOLETE_KEYS.forEach( oKey -> { @@ -729,6 +736,38 @@ public class SettingsService { setBoolean(KEY_SORT_ALBUMS_BY_YEAR, b); } + public boolean getIgnoreSymLinks() { + return getBoolean(KEY_IGNORE_SYMLINKS, DEFAULT_IGNORE_SYMLINKS); + } + + public void setIgnoreSymLinks(boolean b) { + setBoolean(KEY_IGNORE_SYMLINKS, b); + } + + public String getExcludePatternString() { + return getString(KEY_EXCLUDE_PATTERN_STRING, DEFAULT_EXCLUDE_PATTERN_STRING); + } + + public void setExcludePatternString(String s) { + setString(KEY_EXCLUDE_PATTERN_STRING, s); + compileExcludePattern(); + } + + private void compileExcludePattern() { + if (getExcludePatternString() != null && getExcludePatternString().trim().length() > 0) { + excludePattern = Pattern.compile(getExcludePatternString()); + } else { + excludePattern = null; + } + } + + public Pattern getExcludePattern() { + if (excludePattern == null && getExcludePatternString() != null) { + compileExcludePattern(); + } + return excludePattern; + } + public MediaLibraryStatistics getMediaLibraryStatistics() { return MediaLibraryStatistics.parse(getString(KEY_MEDIA_LIBRARY_STATISTICS, DEFAULT_MEDIA_LIBRARY_STATISTICS)); } 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 f1a015bc..d24cae72 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 @@ -398,6 +398,8 @@ musicfoldersettings.nowscanning=The media folders are now being scanned. This op musicfoldersettings.scannow=Scan media folders now musicfoldersettings.access=Manage user access musicfoldersettings.access.description=Configure which folders each user is allowed to access. +musicfoldersettings.ignoresymlinks=Ignore Symbolic Links +musicfoldersettings.excludepattern=Exclude pattern musicfoldersettings.fastcache=Fast access mode musicfoldersettings.fastcache.description=Use this option to minimize disk access, for instance if your media files are located on a network disk. Note: Changed or added files will only be visible after your media folders are scanned. musicfoldersettings.expunge=Clean-up database @@ -660,6 +662,8 @@ helppopup.jndiname.title=Data Source JNDI Lookup Name helppopup.jndiname.text=A JNDI name to lookup a Data Source of type javax.sql.DataSource. This is something that iscreated in your application container (i.e. tomcat). helppopup.embeddriver.title=JDBC Driver Class helppopup.embeddriver.text=JDBC Driver dependent class name that implments java.sql.Driver. I.E. for postgres one would use org.postgresql.Driver. This class must be present on the classpath. +helppopup.excludepattern.title=Exclude Pattern +helppopup.excludepattern.text=

Airsonic will not import any files which match this regular expression pattern.

helppopup.playlistfolder.title=Import playlist from helppopup.playlistfolder.text=

Airsonic will regularly import playlists stored in this folder.

helppopup.musicmask.title=Music files diff --git a/airsonic-main/src/main/webapp/WEB-INF/jsp/generalSettings.jsp b/airsonic-main/src/main/webapp/WEB-INF/jsp/generalSettings.jsp index 173024ab..186b6454 100644 --- a/airsonic-main/src/main/webapp/WEB-INF/jsp/generalSettings.jsp +++ b/airsonic-main/src/main/webapp/WEB-INF/jsp/generalSettings.jsp @@ -172,4 +172,4 @@ - \ No newline at end of file + diff --git a/airsonic-main/src/main/webapp/WEB-INF/jsp/musicFolderSettings.jsp b/airsonic-main/src/main/webapp/WEB-INF/jsp/musicFolderSettings.jsp index 6eb51e9a..1512b590 100644 --- a/airsonic-main/src/main/webapp/WEB-INF/jsp/musicFolderSettings.jsp +++ b/airsonic-main/src/main/webapp/WEB-INF/jsp/musicFolderSettings.jsp @@ -69,6 +69,17 @@

+
+ + + +
+ +
+ + +
+
@@ -133,4 +144,4 @@ - \ No newline at end of file +