Issue 455

(570) Adds a settings option to ignore symlinks during the media scan.

(4550 Adds a regular expression pattern to exclude in addition to the option
of excluding symlinks.
master
Allen Petersen 7 years ago
parent 6fc6d90351
commit 31aca4fcdf
  1. 20
      airsonic-main/src/main/java/org/airsonic/player/command/MusicFolderSettingsCommand.java
  2. 6
      airsonic-main/src/main/java/org/airsonic/player/controller/MusicFolderSettingsController.java
  3. 11
      airsonic-main/src/main/java/org/airsonic/player/service/MediaFileService.java
  4. 41
      airsonic-main/src/main/java/org/airsonic/player/service/SettingsService.java
  5. 4
      airsonic-main/src/main/resources/org/airsonic/player/i18n/ResourceBundle_en.properties
  6. 2
      airsonic-main/src/main/webapp/WEB-INF/jsp/generalSettings.jsp
  7. 13
      airsonic-main/src/main/webapp/WEB-INF/jsp/musicFolderSettings.jsp

@ -41,6 +41,8 @@ public class MusicFolderSettingsCommand {
private boolean organizeByFolderStructure; private boolean organizeByFolderStructure;
private List<MusicFolderInfo> musicFolders; private List<MusicFolderInfo> musicFolders;
private MusicFolderInfo newMusicFolder; private MusicFolderInfo newMusicFolder;
private String excludePatternString;
private boolean ignoreSymLinks;
public String getInterval() { public String getInterval() {
return interval; return interval;
@ -98,6 +100,22 @@ public class MusicFolderSettingsCommand {
this.organizeByFolderStructure = organizeByFolderStructure; 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 { public static class MusicFolderInfo {
private Integer id; private Integer id;
@ -176,4 +194,4 @@ public class MusicFolderSettingsCommand {
return existing; return existing;
} }
} }
} }

@ -85,6 +85,8 @@ public class MusicFolderSettingsController {
command.setScanning(mediaScannerService.isScanning()); command.setScanning(mediaScannerService.isScanning());
command.setMusicFolders(wrap(settingsService.getAllMusicFolders(true, true))); command.setMusicFolders(wrap(settingsService.getAllMusicFolders(true, true)));
command.setNewMusicFolder(new MusicFolderSettingsCommand.MusicFolderInfo()); command.setNewMusicFolder(new MusicFolderSettingsCommand.MusicFolderInfo());
command.setExcludePatternString(settingsService.getExcludePatternString());
command.setIgnoreSymLinks(settingsService.getIgnoreSymLinks());
model.addAttribute("command",command); model.addAttribute("command",command);
} }
@ -123,6 +125,8 @@ public class MusicFolderSettingsController {
settingsService.setIndexCreationHour(Integer.parseInt(command.getHour())); settingsService.setIndexCreationHour(Integer.parseInt(command.getHour()));
settingsService.setFastCacheEnabled(command.isFastCache()); settingsService.setFastCacheEnabled(command.isFastCache());
settingsService.setOrganizeByFolderStructure(command.isOrganizeByFolderStructure()); settingsService.setOrganizeByFolderStructure(command.isOrganizeByFolderStructure());
settingsService.setExcludePatternString(command.getExcludePatternString());
settingsService.setIgnoreSymLinks(command.getIgnoreSymLinks());
settingsService.save(); settingsService.save();
@ -133,4 +137,4 @@ public class MusicFolderSettingsController {
return "redirect:musicFolderSettings.view"; return "redirect:musicFolderSettings.view";
} }
} }

@ -38,6 +38,7 @@ import org.springframework.stereotype.Service;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files;
import java.util.*; import java.util.*;
/** /**
@ -451,9 +452,17 @@ public class MediaFileService {
* @return Whether the child file is excluded. * @return Whether the child file is excluded.
*/ */
private boolean isExcluded(File file) { 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). // 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"); return (name.startsWith(".") && !name.startsWith("..")) || name.startsWith("@eaDir") || name.equals("Thumbs.db");
} }

@ -42,6 +42,7 @@ import java.io.InputStream;
import java.util.*; import java.util.*;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap; 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_PASSWORD = "SmtpPassword";
private static final String KEY_SMTP_FROM = "SmtpFrom"; private static final String KEY_SMTP_FROM = "SmtpFrom";
private static final String KEY_EXPORT_PLAYLIST_FORMAT = "PlaylistExportFormat"; 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 // Database Settings
private static final String KEY_DATABASE_CONFIG_TYPE = "DatabaseConfigType"; 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 String DEFAULT_SONOS_SERVICE_NAME = "Airsonic";
private static final int DEFAULT_SONOS_SERVICE_ID = 242; private static final int DEFAULT_SONOS_SERVICE_ID = 242;
private static final String DEFAULT_EXPORT_PLAYLIST_FORMAT = "m3u"; 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_SERVER = null;
private static final String DEFAULT_SMTP_ENCRYPTION = "None"; private static final String DEFAULT_SMTP_ENCRYPTION = "None";
@ -200,7 +205,7 @@ public class SettingsService {
// Array of obsolete keys. Used to clean property file. // Array of obsolete keys. Used to clean property file.
private static final List<String> OBSOLETE_KEYS = Arrays.asList("PortForwardingPublicPort", "PortForwardingLocalPort", private static final List<String> OBSOLETE_KEYS = Arrays.asList("PortForwardingPublicPort", "PortForwardingLocalPort",
"DownsamplingCommand", "DownsamplingCommand2", "DownsamplingCommand3", "AutoCoverBatch", "MusicMask", "DownsamplingCommand", "DownsamplingCommand2", "DownsamplingCommand3", "AutoCoverBatch", "MusicMask",
"VideoMask", "CoverArtMask, HlsCommand", "HlsCommand2", "JukeboxCommand", "VideoMask", "CoverArtMask, HlsCommand", "HlsCommand2", "JukeboxCommand",
"CoverArtFileTypes", "UrlRedirectCustomHost", "CoverArtLimit", "StreamPort", "CoverArtFileTypes", "UrlRedirectCustomHost", "CoverArtLimit", "StreamPort",
"PortForwardingEnabled", "RewriteUrl", "UrlRedirectCustomUrl", "UrlRedirectContextPath", "PortForwardingEnabled", "RewriteUrl", "UrlRedirectCustomUrl", "UrlRedirectContextPath",
"UrlRedirectFrom", "UrlRedirectionEnabled", "UrlRedirectType", "Port", "HttpsPort", "UrlRedirectFrom", "UrlRedirectionEnabled", "UrlRedirectType", "Port", "HttpsPort",
@ -233,6 +238,8 @@ public class SettingsService {
private List<MusicFolder> cachedMusicFolders; private List<MusicFolder> cachedMusicFolders;
private final ConcurrentMap<String, List<MusicFolder>> cachedMusicFoldersPerUser = new ConcurrentHashMap<>(); private final ConcurrentMap<String, List<MusicFolder>> cachedMusicFoldersPerUser = new ConcurrentHashMap<>();
private Pattern excludePattern;
private void removeObsoleteProperties() { private void removeObsoleteProperties() {
OBSOLETE_KEYS.forEach( oKey -> { OBSOLETE_KEYS.forEach( oKey -> {
@ -729,6 +736,38 @@ public class SettingsService {
setBoolean(KEY_SORT_ALBUMS_BY_YEAR, b); 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() { public MediaLibraryStatistics getMediaLibraryStatistics() {
return MediaLibraryStatistics.parse(getString(KEY_MEDIA_LIBRARY_STATISTICS, DEFAULT_MEDIA_LIBRARY_STATISTICS)); return MediaLibraryStatistics.parse(getString(KEY_MEDIA_LIBRARY_STATISTICS, DEFAULT_MEDIA_LIBRARY_STATISTICS));
} }

@ -398,6 +398,8 @@ musicfoldersettings.nowscanning=The media folders are now being scanned. This op
musicfoldersettings.scannow=Scan media folders now musicfoldersettings.scannow=Scan media folders now
musicfoldersettings.access=Manage user access musicfoldersettings.access=Manage user access
musicfoldersettings.access.description=Configure which folders each user is allowed to 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=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.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 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.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.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.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=<p>Airsonic will not import any files which match this regular expression pattern.</p>
helppopup.playlistfolder.title=Import playlist from helppopup.playlistfolder.title=Import playlist from
helppopup.playlistfolder.text=<p>Airsonic will regularly import playlists stored in this folder.</p> helppopup.playlistfolder.text=<p>Airsonic will regularly import playlists stored in this folder.</p>
helppopup.musicmask.title=Music files helppopup.musicmask.title=Music files

@ -172,4 +172,4 @@
</script> </script>
</c:if> </c:if>
</body></html> </body></html>

@ -69,6 +69,17 @@
<fmt:message key="musicfoldersettings.access.description"/> <fmt:message key="musicfoldersettings.access.description"/>
</p> </p>
<div>
<fmt:message key="musicfoldersettings.excludepattern"/>
<form:input path="excludePatternString" size="70"/>
<c:import url="helpToolTip.jsp"><c:param name="topic" value="excludepattern"/></c:import>
</div>
<div>
<form:checkbox path="ignoreSymLinks" id="ignoreSymLinks"/>
<form:label path="ignoreSymLinks"><fmt:message key="musicfoldersettings.ignoresymlinks"/></form:label>
</div>
<div style="padding-top: 0.5em;padding-bottom: 0.3em"> <div style="padding-top: 0.5em;padding-bottom: 0.3em">
<span style="white-space: nowrap"> <span style="white-space: nowrap">
<fmt:message key="musicfoldersettings.scan"/> <fmt:message key="musicfoldersettings.scan"/>
@ -133,4 +144,4 @@
</form:form> </form:form>
</body></html> </body></html>

Loading…
Cancel
Save